Building Fault-Tolerant Webhook Handlers for SSLCommerz & bKash in Laravel 12
Official Verified Solution
Solved by Fahim Chowdhury
We use database-level unique constraint on transactions.gateway_reference_id combined with DB::transaction() and pessimistic locking (lockForUpdate()).
$invoice = Invoice::query()->where('id', $invoiceId)->lockForUpdate()->first();
if ($invoice && $invoice->status !== 'Paid') {
$invoice->markAsPaid($gatewayRef);
}
This prevents race conditions completely even if both user redirect and gateway IPN hit the server at the exact same millisecond! ๐ก๐ฅ
When integrating Bangladeshi payment gateways (SSLCommerz IPN, bKash webhook callback, Nagad notification), network drops and duplicate IPN triggers are common challenges.
Best Practice Architecture:
- Atomic Idempotency Keys: Use
invoice_id+transaction_idcache locks before dispatching order fulfillment jobs. - Immediate 200 OK Return: Acknowledge the webhook immediately within 200ms and defer database writes and provisioning to a background queue worker (
php artisan queue:work redis). - Signature Verification: Validate SHA256 / RSA signatures before processing payload.
Here is a simplified controller sample:
public function handleIpn(Request $request): Response
{
$payload = $request->validate([
'tran_id' => 'required|string',
'val_id' => 'required|string',
'status' => 'required|string',
]);
if ($payload['status'] !== 'VALID') {
return response('Invalid Status', 400);
}
ProcessPaymentIpnJob::dispatch($payload)->onQueue('payments');
return response('IPN Received', 200);
}
How do you handle signature verification discrepancies when gateways change TLS versions?
We use database-level unique constraint on transactions.gateway_reference_id combined with DB::transaction() and pessimistic locking (lockForUpdate()).
$invoice = Invoice::query()->where('id', $invoiceId)->lockForUpdate()->first();
if ($invoice && $invoice->status !== 'Paid') {
$invoice->markAsPaid($gatewayRef);
}
This prevents race conditions completely even if both user redirect and gateway IPN hit the server at the exact same millisecond! ๐ก๐ฅ
Excellent discussion! Combining Redis queue dispatch with lockForUpdate() is the gold standard for financial reliability in Laravel. ๐โ
Marked @fahim.chowdhury's reply as best answer. The lockForUpdate() strategy is essential for concurrent IPN callbacks! ๐
Join the Discussion
Please log in to your client account to reply or participate in this conversation.