Skip to main content
Webhooks let Tender notify your server when something happens in your account, such as a payment being completed or a deposit arriving at a subwallet address. Tender sends an HTTPS POST with a JSON body to the URL you register. For the list of events and their payloads, see Webhook Events.

Register your webhook

Register your endpoint with POST /v1/api/webhook. This is the only way to register a webhook. Payment and other API requests don’t accept a callback URL.
  • One webhook per merchant account. Calling POST /v1/api/webhook again replaces your existing URL, description, event list and headers.
  • Subscribe to each event by name. You only receive events listed in eventTypes. Event names are case-sensitive and lowercase (for example transaction_completed). Names are not checked when you subscribe, so a misspelt name is accepted but never delivered. Use GET /v1/api/webhook/events to get the exact names.
  • Use a public HTTPS URL. The URL must use https, and must not point to localhost or a private, loopback or link-local address.
To see your current registration, call GET /v1/api/webhook. To stop all deliveries, call DELETE /v1/api/webhook/{id}.

Webhook structure

Every webhook has the same envelope: Example (data shortened):
The request is sent with Content-Type: application/json and Accept: application/json, plus any custom headers you registered. Payloads may gain new fields over time; ignore fields you don’t recognise.

Responding to webhooks

  • Return any 2xx status to acknowledge the webhook. Any other status, a redirect, a connection error or no response within 15 seconds counts as a failed delivery.
  • Respond quickly. Acknowledge first, then process the event asynchronously (for example, by putting it on a queue).
  • Redirects are not followed. Register the final URL, not one that redirects.

No automatic retries

Tender sends each webhook once. Failed deliveries are not retried. Every delivery attempt, successful or failed, is recorded in your webhook logs. Use webhooks as a fast signal, and don’t rely on them alone:
  • Check the webhook logs for failed deliveries.
  • For payments, confirm the state with validate payment when a webhook is missing or you need certainty.
  • Reconcile periodically against the API.

Deduplicating events

You can receive more than one webhook for the same thing. For example, transaction_partially_paid can arrive several times while a payment stays partially paid, and sub_user_wallet_transaction_detected arrives again each time a deposit’s confirmation count changes. eventId is new on every delivery, so it can’t be used to detect duplicates. Deduplicate on the business identifiers instead: Make your handler idempotent: processing the same event twice should have no extra effect.

Securing your endpoint

Tender webhooks are not signed. There is no signature header to verify. Instead, authenticate deliveries with the custom headers you register: Tender sends them, unchanged, with every webhook.

Setting custom headers

Register a secret value in headers when you create the webhook:
See Authentication for signedHeaders. A few headers can’t be overridden and are dropped if you set them: Host, Content-Length, Connection, Transfer-Encoding, Upgrade, Proxy-Authorization, X-Forwarded-For, X-Forwarded-Host and X-Forwarded-Proto.

Verifying custom headers

On your endpoint, reject any request that doesn’t carry the expected value:

Best practices

  • Use a long, randomly generated secret value, and compare it in constant time.
  • Keep the value out of source control, and rotate it by calling POST /v1/api/webhook with a new value.
  • Treat the payload as a notification. Before you ship goods or credit a customer, confirm the payment with validate payment.
  • Serve the endpoint over HTTPS only.

What doesn’t send webhooks

There are no webhooks for payouts, conversions or on-ramp orders created through the API. Poll their GET endpoints to track status, within the rate limits: