Skip to main content
Taberna delivers order and invoice lifecycle events to one HTTP endpoint per store, through a durable outbox and a retrying dispatcher. This — not a browser redirect, not a polling loop — is what you should fulfil on.

Setting up

Webhooks are configured in the dashboard, by a store owner, admin or manager.

Set the URL

Point it at an HTTPS endpoint you control. A signing secret is generated on first setup and stays readable in the dashboard — unlike an API key, which is shown once and never again, you can come back for the signing secret whenever you need it.

Keep the secret across URL changes

Changing the URL later keeps the existing secret, so you can move infrastructure without a secret rollout. Rotating the secret is a separate, explicit action.

Verify before you rely on it

The secret is always tbrn_whsec_ followed by 48 hexadecimal characters. The whole string, prefix included, is the HMAC key — do not strip the prefix.
Delivery is at-least-once and failures are retried for days. Everything about retries, resends and writing a handler that survives a duplicate is on Webhook reliability.

Events

Ten event names, five per resource.
order.processing fires only for the first attempt on an order. A buyer who lets a quote lapse and picks a token again does not re-fire it, and there is no invoice.processing counterpart. Abandoning an attempt by switching tokens emits nothing at all — it is a UI affordance, not a lifecycle event.

Request format

Taberna sends POST with these headers:

Order payload

Identical shape for every order.* event.
  • data.id is the order’s public short id — the same value POST /v1/orders returned.
  • data.extraContext is exactly what you passed at creation. This is how you find your user.
  • data.items is what was bought, snapshotted at purchase time, so later product edits never rewrite a past order. amount is the line total, unitPrice × quantity.
  • priceAmount, items[].unitPrice and items[].amount are fiat decimal strings; cryptoAmount and receivedAmount are crypto smallest-unit strings.
  • paymentMethod, cryptoAmount, receivedAmount and ipAddress come from the payment attempt that triggered the event and are null on events with no attempt yet — order.created, for instance, where transactions is [] too.

Invoice payload

Identical shape for every invoice.* event.
  • data.metadata is exactly what you passed at creation.
  • payment is null on events with no attempt involved — invoice.created and invoice.voided, for instance. Note the different nesting from the order payload, where the same facts are flat on data.
transactions is best effort. An empty array means Taberna could not attribute a transfer — a gap in the chain node Taberna reads from, or a native transfer made from inside a contract — not that no money moved. Never treat an empty array as evidence of non-payment. The authoritative fact is status.

Verifying the signature

The signature is:
in lowercase hex, where timestamp is the X-Taberna-Timestamp header (unix seconds) and rawBody is the exact raw request body. This is the same scheme Stripe uses, so Stripe-shaped verification code ports directly.
Verify against the raw bytes, before JSON parsing. Almost every failed integration is a framework that parsed and re-serialized the body first: a single whitespace or key-order difference changes the hash and every signature check fails.In Express, mount express.raw({ type: 'application/json' }) on the webhook route. In FastAPI, use await request.body(). In Laravel, use $request->getContent(). In Next.js App Router, use await req.text().
Two rules the samples encode:
  1. Constant-time comparison. timingSafeEqual, hmac.compare_digest, hash_equals — never ==.
  2. A timestamp tolerance. Taberna does not enforce one for you; reject stale timestamps yourself. Five minutes is the recommended window, matching what Stripe and Slack document. Without it, a captured request stays replayable forever.
If verification fails, respond non-2xx (or simply ignore the request). Do not process the payload.

Webhook reliability

Retries, at-least-once delivery, the idempotent handler, list and resend.

Payment lifecycle

What each event means in terms of the underlying state machines.