Pinned Solved

Building Fault-Tolerant Webhook Handlers for SSLCommerz & bKash in Laravel 12

โ€ข 13-Sep-2026 10:48 AM (3 weeks ago)
3 Replies
564 Views

Official Verified Solution

Solved by Fahim Chowdhury

Jump to Answer ↓

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! ๐Ÿ’ก๐Ÿ”ฅ

K
13-Sep-2026 10:48 AM โ€ข 90 Karma

When integrating Bangladeshi payment gateways (SSLCommerz IPN, bKash webhook callback, Nagad notification), network drops and duplicate IPN triggers are common challenges.

Best Practice Architecture:

  1. Atomic Idempotency Keys: Use invoice_id + transaction_id cache locks before dispatching order fulfillment jobs.
  2. 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).
  3. 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?

F
14-Sep-2026 12:48 PM โ€ข 30 Karma

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! ๐Ÿ’ก๐Ÿ”ฅ

M
15-Sep-2026 03:48 PM โ€ข 115 Karma

Excellent discussion! Combining Redis queue dispatch with lockForUpdate() is the gold standard for financial reliability in Laravel. ๐Ÿš€โœ…

K
16-Sep-2026 11:48 AM โ€ข 90 Karma

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.

Log in to Reply
0%