Skip to main content

Payment Callback

The payment callback delivers the result of a payment to your server. It is delivered to your callback_url — as a redirect for redirect integrations, or as a direct POST for no-redirect integrations. For the checkout callback (the browser-side response), see Checkout Callback. For configuration and signature validation shared by both callbacks, see Callback Payloads.

Migration From v3 to v4

Moving a sub-merchant to v4? See Migrating Callbacks from v3 to v4 for the payload, signature, and rollout changes.

The v4 payment callback carries a compact {order, transaction} payload, Base64-encoded and signed.

How Nimbbl Prepares the Payload

  1. Nimbbl builds the payment result as a compact JSON object with an order object and a transaction object.
  2. That JSON is serialised to a compact string (no extra spaces). It is Base64-encoded into the payload field, and HMAC-SHA256 signed with your access_secret into the nimbbl_signature field.
  3. If encryption is enabled, the signed result is AES-GCM encrypted and delivered as encrypted_response, along with your sub_merchant_id.

What You Receive

The fields are the same either way — payload, nimbbl_signature, version — but how they arrive depends on your callback mode:

Redirect integrations — Nimbbl redirects the browser to your callback_url with the fields spread as query parameters, not a POST body:

callback_url?payload=<base64>&nimbbl_signature=<hmac-sha256-hex>&version=v4

No-redirect / webhook integrations (callback_mode is callback_url_noredirect or callback_handler) — Nimbbl POSTs the same fields as a JSON body to your callback_url:

callback_mode Has Four Values

callback_mode is set per order and determines how the result reaches the client: callback_handler (posts a message to the parent frame), callback_url_redirect (redirects the browser with the payload in the URL), callback_mobile (used by the mobile SDKs — redirects to an internal mobile handoff route), and callback_url_noredirect (the default — closes the checkout window). This callback page describes the server-side result Nimbbl also POSTs to your callback_url, which is independent of callback_mode. Two failure modes are silent: an unrecognised callback_mode value falls back to closing the window with no callback delivered, and a missing callback_mode throws client-side, so the browser interaction never completes — validate that callback_mode is set correctly on every order.

{
"payload": "<base64>",
"nimbbl_signature": "<hmac-sha256-hex>",
"version": "v4"
}

payload is the Base64-encoded compact JSON below; nimbbl_signature is the HMAC-SHA256 of that compact JSON string — not of individual extracted fields, and not of the query string itself. Validate it the same way regardless of whether it arrived as query parameters or a JSON body.

Multi-sub-merchant Attribution

Unlike the webhook, the unencrypted v4 payment callback does not include sub_merchant_id — the checkout callback works the same way. If you run multiple sub-merchants against a single callback URL, there's nothing in the response telling you which access_secret to verify against. Route by URL (one callback URL per sub-merchant) or enable encryption, which does add sub_merchant_id.

When encryption is enabled, both delivery modes carry the encrypted variant instead — as query parameters (?encrypted_response=<hex>&sub_merchant_id=<id>) for redirects, or as a JSON body for no-redirect/webhook:

{
"encrypted_response": "<hex>",
"sub_merchant_id": "<id>"
}

Decoded payload — Unencrypted

Base64-decode the payload value to get the compact JSON string, then parse it. The example below shows a pre-authorization result:

{
"order": {
"invoice_id": "<Merchant invoice id>",
"order_id": "<Order_ID>",
"currency": "INR",
"total_amount": 5000.00,
"status": "lapsed",
"cancellation_reason": null,
"lapsed_reason": "payment_authorized"
},
"transaction": {
"transaction_id": "<Transaction_ID>",
"status": "authorized",
"transaction_type": "payment",
"transaction_amount": 5500.00,
"transaction_currency": "INR",
"nimbbl_error_code": "",
"nimbbl_consumer_message": "",
"nimbbl_merchant_message": "",
"authorization_details": {
"mechanism": "pre_auth",
"capture_mode": "manual",
"authorized_time": "2026-06-18 10:52:26",
"expiry_time": "2026-06-25 10:52:26",
"captured_amount": 0.00,
"voided_amount": 0.00,
"available_authorized_amount": 5500.00
}
}
}

Here the transaction status is authorized and the order status is lapsed with lapsed_reason payment_authorized. The funds are held, not collected — see the callout below.

authorization_details

The transaction.authorization_details block is present only for pre-authorization payments — that is, payment transactions where the sub-merchant's capture_mode is manual. For automatically captured payments it is omitted entirely.

FieldTypeDescription
mechanismstringAlways pre_auth for a pre-authorized transaction
capture_modestringmanual — the authorization must be captured explicitly
authorized_timestring | nullWhen the authorization was granted
expiry_timestring | nullWhen the authorization lapses if not captured
captured_amountnumberTotal already captured from this authorization (successful captures only)
voided_amountnumberTotal already voided from this authorization (successful voids only)
available_authorized_amountnumberAmount still available to capture or void
An Authorized Result Is Not a Completed Payment

When a transaction comes back with status: "authorized", the funds are only held on the customer's card — they have not been collected. You must capture the authorization before fulfilling the order, and you must not treat this callback as a successful payment.

Always confirm the outcome via the payment_authorized webhook or the Transaction Enquiry API before acting. For the full flow, see Pre-Authorization and Pre-Auth and Capture.

Optional Fields

order.cancellation_reason and order.lapsed_reason are null unless the order was cancelled or lapsed. transaction.authorization_details appears only for pre-authorization (manual-capture) payments.

Decoded payload — Encrypted

If your account has encryption enabled, the v4 payment callback is delivered as an encrypted result — and no nimbbl_signature is sent:

{
"encrypted_response": "3164351ca6195e9871...",
"sub_merchant_id": "123456"
}

Important: Decrypt encrypted_response using your AES-GCM key to obtain the plain {order, transaction} object directly — there is no Base64 payload field and no signature to validate. A successful AES-GCM decryption is itself proof of authenticity. See Using Encrypted Payloads for decryption steps and code.