Payment Webhooks
Payment webhooks let Bead send real-time payment status updates to your system. Instead of polling, your server receives an HTTP POST whenever a payment's statusCode changes.
Payment webhook notifications are typically used together with the Payments and Reporting APIs.
When to use payment webhooks
Use payment webhooks when you want to:
update orders or invoices as soon as a payment completes
react to
underpaid,overpaid,expired,invalid, orcancelledoutcomestrigger downstream workflows such as fulfilment, refunds, customer notifications, or support tickets
track payments without polling
GET /Payments/{paymentId}/tracking
How payment webhooks work
You configure a default webhook URL for a terminal, or provide
webhookUrlswhen creating a payment.Bead delivers an HTTP
POSTwhen a payment status changes.Your server verifies the webhook signature using the stored
signingSecret.Your system records the event and updates internal state.
If needed, your system confirms the latest state using
GET /Payments/{paymentId}/tracking.
Configure payment webhook delivery
Payment webhooks are configured per terminal.
Default terminal webhook
Use the terminal webhook endpoint to set the default payment webhook URL for a terminal:
PUT /Terminals/{id}/webhook
The webhook URL should be an HTTPS endpoint that you control.
A successful setup response includes:
urlsigningSecret
Store signingSecret securely. You will use it to verify incoming payment webhooks.
Optional per-payment webhook URLs
When creating a payment with POST /Payments/crypto, you can also provide webhookUrls for payment-specific delivery in addition to the terminal's default webhook.
Use this when you want a payment to notify a specific backend flow without changing the terminal's default webhook configuration.
Where to get the signing secret
The payment webhook signing secret is associated with the terminal webhook configuration and should be stored securely when the webhook is set.
Important notes:
do not expose
signingSecretin client-side codedo not expect the incoming webhook payload to include the secret
do not compare the incoming signature header directly to the secret itself
The correct verification pattern is:
configure the webhook
save the
signingSecretreceive the webhook delivery
verify the signature using the steps below
only trust the event if verification passes
Event delivery
For each payment update, Bead sends a POST request to your webhook URL.
General behavior:
method:
POSTcontent type:
application/jsonone event per payment status change
retries occur if your endpoint does not return a successful
2xxresponse
Your webhook handler should:
log every webhook request for debugging and audit
preserve the raw request body before JSON parsing
preserve request headers
verify the signature before processing the payload
return a successful response quickly after safely persisting or queueing the event
Current payment signature header
Current payment webhook deliveries include the signature in:
Example:
t
Unix epoch timestamp in milliseconds when Bead generated the event
s
Base64-encoded HMAC-SHA256 digest of the signed message t + "." + rawBody, computed using the decoded bytes of the terminal's signingSecret
Verifying the webhook
Always verify the payment webhook before processing the JSON body.
Capture the raw request body exactly as received. Do not parse or reserialize JSON before this step.
Read the
x-webhook-signatureheader and parse thetandsvalues.Validate the timestamp by confirming
t(in milliseconds) is within 5 minutes of the current time. Reject requests outside this window to prevent replay attacks.Decode
signingSecretfrom base64 to raw bytes. Use those bytes as the HMAC key.Construct the signed message by concatenating
t, a literal period, and the raw request body:message = t + "." + rawBody.Compute
HMAC-SHA256(key=decodedSecretBytes, message=message)and base64-encode the digest.Compare your computed base64 digest to
susing constant-time equality. Reject the request if they do not match.
For a full code example, see How do I verify that a webhook really came from Bead?
Example payload
Common payment status values
Payment webhook events may include status values such as created, processing, underpaid, overpaid, completed, expired, invalid, and cancelled.
Your system should treat statusCode as the primary event classifier.
Key fields
paymentId
This is the primary identifier for the payment. Use it to call GET /Payments/{paymentId}/tracking to confirm or refresh payment state and correlate status checks with your internal payment or order record.
trackingId
This is the tracking identifier returned when the payment was created. Use it to correlate the webhook to your internal payment or order record, reconcile webhook delivery with reporting and support workflows, and key idempotency checks alongside statusCode.
paymentCode
This is the hosted payment code associated with the checkout.
statusCode
This tells you the payment's current lifecycle state.
terminalId
This identifies the terminal that produced the payment.
merchantId
This identifies the merchant associated with the payment.
receivedTime
This is the timestamp associated with the event payload.
Recommended processing model
Receive the request.
Preserve the raw request body and headers.
Read
x-webhook-signatureand parsetands.Validate the timestamp — confirm
t(in milliseconds) is within 5 minutes of the current time.Decode
signingSecretfrom base64 to bytes. Build the signed message ast + "." + rawBody. ComputeHMAC-SHA256(key=decodedSecretBytes, message=signedMessage)and base64-encode the digest.Compare the computed digest to
susing constant-time equality. Reject the request if they do not match.Parse the JSON payload only after verification succeeds.
Persist the event or enqueue it for processing.
Update your internal order or invoice state based on
statusCode.Return a successful
2xxresponse quickly.
Idempotency and duplicate handling
Treat payment webhook delivery as at least once.
A good idempotent processing strategy uses trackingId + statusCode as the primary key, with the raw body hash or received timestamp optionally stored for debugging.
Your system should be able to safely ignore duplicate deliveries without creating duplicate business actions.
Delivery mechanics
respond quickly, ideally in under one second when possible
do heavy work asynchronously after the event is safely persisted or queued
expect retries with backoff when your endpoint fails or times out
rely on signature verification rather than source IP allowlisting unless Bead explicitly publishes and supports source IP controls for your environment
Confirming status with the Tracking endpoint
If you need to confirm the latest state during support, reconciliation, or after a missed event, call the tracking endpoint using the payment's paymentId.
Endpoint
GET /Payments/{paymentId}/tracking
Headers
X-Api-Key: {apiKey}Accept: application/json
Notes
apiKeyis the secret credentialmaskedApiKeyis not usablethe header name must be exactly
X-Api-Key
Testing webhooks in Sandbox
Configure a webhook URL for your Sandbox terminal.
Create a Sandbox payment with
POST /Payments/crypto.Complete the payment on the hosted page.
Confirm your webhook received payment status updates.
Confirm your listener captures
x-webhook-signature.Verify the signature using the stored
signingSecret.Optionally confirm the final state using
GET /Payments/{paymentId}/tracking.
Troubleshooting
I am receiving the webhook but verification fails
Check:
you are signing
t + "." + rawBody, not the raw body aloneyou are decoding
signingSecretfrom base64 to bytes before using it as the HMAC keyyou are using the raw request body, not a reserialized JSON body
you are producing a base64 digest, not a hex digest
you are using the
signingSecretfor the correct terminal
Webhook not received
Check:
the terminal has a webhook URL configured
your endpoint is publicly reachable over HTTPS
your endpoint returns a successful
2xxresponse quicklyyour server logs show inbound requests and any verification or parsing failures
Receiving repeated webhook events
This usually means your endpoint is timing out or returning a non-2xx response. Return success quickly after safely persisting or queueing the event, and make your processing idempotent.
401 Unauthorized when checking status
Payments endpoints use X-Api-Key. Confirm the API key is present, valid for the environment, and that the header name is exactly X-Api-Key.
403 Forbidden when checking status
The API key is valid but not permitted for the payment context, or you are mixing environments. Confirm you are using the correct environment base URL and the correct terminal API key.
Next steps
Review Payment Statuses for the full list of
statusCodevaluesReview Create Payment for
webhookUrlssupport when you need per-payment deliveryReview Webhook Event Reference for the shared signature and delivery reference
Use Reporting and Settlement for historical views and reconciliation
Last updated