v4 Signature
v4 doesn't join individual fields together — you only need two things:
| Attribute | Description |
|---|---|
payload | The Base64-encoded compact JSON string received in the webhook, payment callback, or checkout callback |
access_secret | Your access secret from Command Center. Available on your server |
Base64-decode payload to get the compact JSON string exactly as Nimbbl sent it, and hash that string directly — you don't pull out or rearrange any fields first. The result is lowercase hex — compare case-insensitively, or lowercase both sides, if your language's HMAC output defaults to uppercase. For the reusable HMAC-SHA256 function in Java, C#, PHP, and Python, see Validating Payment Response with Signature.
- Webhook
- Payment Callback
- Checkout Callback
For the full payload shapes across every event category, see Webhook Payloads.
This works the same way as the v4 payment and checkout callbacks. The only difference is the field name — the webhook puts the signature in a field called signature, not nimbbl_signature.
decoded_payload = base64_decode(payload); // exact compact JSON string, no re-serialization
generated_signature = hmac_sha256(decoded_payload, <your_access_secret>);
if (generated_signature == signature) {
payload is authentic
}
The same rule, with the signature in that same signature field (not nimbbl_signature), applies to payment link webhook events too (payment_link_created, payment_link_sent, and the rest of that event set) — see Payment Link Webhooks. This is a separate mechanism from the Payment Link Enquiry API's payment_link_signature field, which is null on a v4 sub-merchant, with no v4 version of its own.
This covers the server-side result delivered to your callback_url. For the full payload shapes, see Payment Callback.
The payment callback puts payload, nimbbl_signature, and version at the top level — as query parameters on a redirect, or as the same fields in a JSON body for no-redirect/webhook delivery.
decoded_payload = base64_decode(payload); // exact compact JSON string, no re-serialization
generated_signature = hmac_sha256(decoded_payload, <your_access_secret>);
if (generated_signature == nimbbl_signature) {
payload is authentic
}
If payload encryption is enabled for the sub-merchant, the payment callback instead delivers an encrypted_response — with no nimbbl_signature field at all. A successful AES-GCM decryption is itself proof of authenticity. See Using Encrypted Payloads.
This covers the browser-side response the Sonic SDK fires via your callback_handler function. For the full payload shapes, see Checkout Callback.
The checkout callback puts the same pair one level deeper than the payment callback — inside a payload field: { event_type, payload: { payload, nimbbl_signature, version } }. So both the encoded string and the signature live under payload.*.
decoded_payload = base64_decode(payload.payload); // exact compact JSON string, no re-serialization
generated_signature = hmac_sha256(decoded_payload, <your_access_secret>);
if (generated_signature == payload.nimbbl_signature) {
payload is authentic
}
If payload encryption is enabled for the sub-merchant, payload.payload instead decodes to {"encrypted_response": "<hex>"} — with no nimbbl_signature field at all. A successful AES-GCM decryption is itself proof of authenticity. See Using Encrypted Payloads.
v4 doesn't apply to the Transaction Enquiry API. If your sub-merchant is configured on payment_callback_version v4, that endpoint returns null for both nimbbl_signature and signature_version — there is nothing to validate on that surface. See the v4 Signature Behavior section on the general Transaction Enquiry guide for the full explanation.