For the complete documentation index, see llms.txt. This page is also available as Markdown.

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, or cancelled outcomes

  • trigger downstream workflows such as fulfilment, refunds, customer notifications, or support tickets

  • track payments without polling GET /Payments/{paymentId}/tracking

How payment webhooks work

  1. You configure a default webhook URL for a terminal, or provide webhookUrls when creating a payment.

  2. Bead delivers an HTTP POST when a payment status changes.

  3. Your server verifies the webhook signature using the stored signingSecret.

  4. Your system records the event and updates internal state.

  5. 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:

  • url

  • signingSecret

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 signingSecret in client-side code

  • do 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:

  1. configure the webhook

  2. save the signingSecret

  3. receive the webhook delivery

  4. verify the signature using the steps below

  5. 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: POST

  • content type: application/json

  • one event per payment status change

  • retries occur if your endpoint does not return a successful 2xx response

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:

Field
Description

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.

  1. Capture the raw request body exactly as received. Do not parse or reserialize JSON before this step.

  2. Read the x-webhook-signature header and parse the t and s values.

  3. 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.

  4. Decode signingSecret from base64 to raw bytes. Use those bytes as the HMAC key.

  5. Construct the signed message by concatenating t, a literal period, and the raw request body: message = t + "." + rawBody.

  6. Compute HMAC-SHA256(key=decodedSecretBytes, message=message) and base64-encode the digest.

  7. Compare your computed base64 digest to s using 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

  1. Receive the request.

  2. Preserve the raw request body and headers.

  3. Read x-webhook-signature and parse t and s.

  4. Validate the timestamp — confirm t (in milliseconds) is within 5 minutes of the current time.

  5. Decode signingSecret from base64 to bytes. Build the signed message as t + "." + rawBody. Compute HMAC-SHA256(key=decodedSecretBytes, message=signedMessage) and base64-encode the digest.

  6. Compare the computed digest to s using constant-time equality. Reject the request if they do not match.

  7. Parse the JSON payload only after verification succeeds.

  8. Persist the event or enqueue it for processing.

  9. Update your internal order or invoice state based on statusCode.

  10. Return a successful 2xx response 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

  • apiKey is the secret credential

  • maskedApiKey is not usable

  • the header name must be exactly X-Api-Key

Testing webhooks in Sandbox

  1. Configure a webhook URL for your Sandbox terminal.

  2. Create a Sandbox payment with POST /Payments/crypto.

  3. Complete the payment on the hosted page.

  4. Confirm your webhook received payment status updates.

  5. Confirm your listener captures x-webhook-signature.

  6. Verify the signature using the stored signingSecret.

  7. 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 alone

  • you are decoding signingSecret from base64 to bytes before using it as the HMAC key

  • you 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 signingSecret for 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 2xx response quickly

  • your 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

Last updated