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 sendsPOST with these headers:
Order payload
Identical shape for everyorder.* event.
data.idis the order’s public short id — the same valuePOST /v1/ordersreturned.data.extraContextis exactly what you passed at creation. This is how you find your user.data.itemsis what was bought, snapshotted at purchase time, so later product edits never rewrite a past order.amountis the line total,unitPrice×quantity.priceAmount,items[].unitPriceanditems[].amountare fiat decimal strings;cryptoAmountandreceivedAmountare crypto smallest-unit strings.paymentMethod,cryptoAmount,receivedAmountandipAddresscome from the payment attempt that triggered the event and arenullon events with no attempt yet —order.created, for instance, wheretransactionsis[]too.
Invoice payload
Identical shape for everyinvoice.* event.
data.metadatais exactly what you passed at creation.paymentisnullon events with no attempt involved —invoice.createdandinvoice.voided, for instance. Note the different nesting from the order payload, where the same facts are flat ondata.
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: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.
- Constant-time comparison.
timingSafeEqual,hmac.compare_digest,hash_equals— never==. - 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.
What to read next
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.