5 min read

When Your Payment Processor’s Webhook Is Late

When your payment processor’s webhook is late
On this page

When a customer contacts your support team because a $2,400 payout to their bank account has not landed, your first instinct is to go looking for the money.

Take Toby. He sent the payout from his Reef wallet at 8:35 AM. Reef passed the instruction to its payment processor, which accepted the request and now shows the payout as paid. But the processor’s payout-status webhook has not reached Reef, so Toby’s wallet still shows the old balance. Now he is messaging support through the app’s live chat, asking where his money is.

You open Toby’s account in the admin console and check the payout job. Reef sent the instruction, the processor returned 200, and then nothing came back. The webhook that should confirm the payout’s final status is late. Because Reef was waiting for that callback to write the transaction, its ledger has no record for support to inspect.

Every movement should start in the ledger

If the payout is sent before it is recorded in your ledger, the processor can move the money while your ledger still shows nothing. A delayed or missing webhook then leaves the payout recorded in one system but absent from the other.

Until your ledger records the payout, it has no state to expose. Toby’s balance will still read as if nothing happened, there will be no pending transaction for support to inspect, and your support team will have to piece the story together from processor dashboards and job logs.

The right approach is that your ledger should come first in that it records the payout and reserve Toby’s money before sending the instruction to the processor.

Now, if the webhook is late or never arrives, you still have a transaction to inspect. The payout remains pending in your ledger, showing you which transaction is affected. The webhook confirms whether that existing transaction succeeded or failed; it does not create the record.

Reserve the money before sending the payout

To make that pending state visible, create Toby’s payout as an inflight transaction with inflight: true. Blnk checks that Toby has sufficient funds and records the transaction as INFLIGHT. Once the reserve succeeds, you can send the payout to the processor. If it fails, you stop the payout before any money moves outside your system.

const response = await blnk.Transactions.create({
  precise_amount: 240000,
  precision: 100,
  reference: 'wd_toby_20260925_01',
  currency: 'USD',
  source: 'bln_28edb3e5-c168-4127-a1c4-16274e7a28d3',
  destination: '@WorldUSD',
  description: 'Withdrawal to bank',
  inflight: true,
  inflight_commit_date,
});

Toby’s main balance still shows $5,000 because the payout has not settled. But the $2,400 now appears in his inflight_debit_balance, leaving only $2,600 available to spend. You cannot use the same money to fund another payment while the payout is unresolved.

Sending the payout without that reserve leaves the $2,400 spendable. Another payment can use the same funds, leaving you with a deficit when the original payout settles.

Toby’s USD balance in Blnk Cloud, showing $5,000 and a −$2,400 inflight balance
Toby’s balance still reads $5,000 while the $2,400 payout is reserved as an inflight debit.

Now that we have reserved the payout, we have an auditable starting point even when we hear nothing from our payout provider.

Since the transaction is already in your ledger, you can easily find it pending when the customer complains that the payout failed, or catch it during end-of-day reconciliation.

Move the payout to its final state

Every payout eventually succeeds or fails. While the processor handles Toby’s payout, the inflight transaction shows that the money is still reserved and the payout is unresolved. The webhook signals which final state should come next.

If the processor confirms the payout, you commit the inflight transaction to finalize it in your ledger, $2,400 moves out of Toby’s reserved/inflight state and is fully deducted from his main balance.

const response = await blnk.Transactions.updateStatus(
  '{transaction_id}',
  {
    status: 'commit',
    skip_queue: false,
  },
);

If the payment failed, you void the transaction to release the reserved funds; Toby can now spend his $2,400 again.

Inflight history for Toby’s $2,400 payout, showing the transaction as Applied
After commit, Toby’s $2,400 payout shows as Applied.

Handling late provider webhooks

Even when you start from the ledger, your provider webhooks can still be late. The difference here is that you’re able to carefully manage and resolve it when it happens instead of having to deal with a missing transaction.

Instead of leaving the transaction reserved indefinitely, you can tell your ledger to proceed with the transaction and commit it optimistically based on the provider’s expected payout window. This means that if your provider doesn’t send a webhook in say 30 minutes, you’ll assume the payout was successful, and commit it in your ledger.

To do this with Blnk, set an inflight_commit_date when creating the inflight transaction to be 30 minutes away from creation.

const commitAt = new Date(Date.now() + 30 * 60 * 1000);
const inflight_commit_date = commitAt.toISOString().slice(0, 19) + '+00:00';

const response = await blnk.Transactions.create({
  precise_amount: 240000,
  precision: 100,
  reference: 'wd_toby_20260925_01',
  currency: 'USD',
  source: 'bln_28edb3e5-c168-4127-a1c4-16274e7a28d3',
  destination: '@WorldUSD',
  description: 'Withdrawal to bank',
  inflight: true,
  inflight_commit_date,
});

With this in place, if you later find out during reconciliation that the payout was successful, you don’t have to do anything; your ledger already has it committed. However, if the customer never gets the payout and the provider recorded a failed payout, you can easily refund the payout back to the customer in your ledger.

Why commit? Because refunding is a lot easier operationally than recovery, and it protects your business from losing money. If you assume the payout didn’t go through and released the funds reserved, it means the customer can spend it before you find out whether it was simply a lost success webhook or a failed payout.

Related posts