Keys, webhooks, sandbox

Set up webhooks

Subscribe your system to platform events: picking topics from the catalogue, the headers a delivery carries, and how your integrator verifies the signature so the endpoint does not accept anyone's POST.

A webhook lets the platform tell your system that something happened, instead of your system asking every thirty seconds. This article is the technical contract your integrator needs; the wider picture — events, subscriptions, retries and reconciliation — is in Events and webhooks.

Before you begin. You need an HTTPS endpoint that belongs to a system, not to somebody's laptop; the topics you want, chosen from the catalogue rather than guessed; and somewhere to keep the signing secret. If the receiver cannot be reached from the public internet, sort that out first — see Common API problems.

1. Choose the topics from the catalogue

Open Events › Event Catalog and read what exists and what each event carries. Do not guess a topic name.

Topics are lowercase and dot-separated, in one of two forms:

FormExampleUsed by
resource.actionorder.createdCore platform resources
vendor.app.entity.actionrevenexx.baseline.order.createdNamespaced producers, including apps

Subscribe narrowly. A receiver subscribed to everything is a receiver that will one day process an event it should have ignored, and it is also the receiver that falls over when somebody runs a bulk edit across 30,000 products.

2. Create the destination

Go to Webhooks › Outbound and select New webhook. Four fields:

FieldWhat to put in it
NameThe receiving system — ERP order sync, not hook1
Endpoint URLAn https:// address on a system — https://erp.example/hooks/orders
TopicsThe specific topics. There is an All topics (*) option; resist it
Signing secret refThe reference to the secret the receiver will verify with

Then check the detail page: it shows the Endpoint, the Subscribed topics, the Signing secret — which reads unsigned if you left it empty — and the Status.

An unsigned endpoint accepts anyone's POST. If your receiver's URL is guessable, or ever appears in a log, a ticket or a browser history, an unsigned webhook is an open write path into your ERP. Always set a signing secret, and always verify it on the receiving side.

3. What a delivery looks like

A delivery is an HTTP POST with a JSON body and four headers:

POST /hooks/orders HTTP/1.1
Content-Type: application/json
X-Revenexx-Id: evt_01jc8m4yq2…
X-Revenexx-Topic: order.created
X-Revenexx-Timestamp: 1748685600
X-Revenexx-Signature: v0=9f86d081884c7d659a2feaa0c55ad015a…

And the body carries a consistent envelope:

{
  "tenant_id": "acme-eu",
  "topic": "order.created",
  "id": "evt_01jc8m4yq2…",
  "data": { "order_id": "o_8421", "total": 14990, "currency": "EUR" },
  "metadata": { "source": "checkout" },
  "time": "2026-05-31T10:00:00Z"
}
The envelope is real and stable; the contents of data are illustrative. What sits inside data depends on the topic and on the app that produced it — read the actual shape from Events › Event Catalog and your tenant's openapi.json, not from this example.

Two properties of id matter to whoever writes the receiver: it is unique per event, and it is stable across retries. It is therefore the deduplication key, and it is the only correct one.

4. Verify the signature

This is the part that is worth getting exactly right.

AspectValue
AlgorithmHMAC-SHA256
EncodingHexadecimal
Signed contentThe raw request body, exactly as received
HeaderX-Revenexx-Signature: v0=<hex>
SecretThe per-endpoint signing secret, of the form whsec_…

The receiver's job, in order:

  1. Take the raw bytes of the body — before any JSON parsing, before any framework re-serialises it. A body that has been decoded and re-encoded will not match, and this is the single most common cause of "the signature is always wrong".
  2. Compute HMAC-SHA256 over those bytes with the signing secret.
  3. Compare in constant time against the hex value after v0=. A plain string comparison leaks timing information; every language has a constant-time compare.
  4. Handle multiple signatures. During a secret rotation the header can carry more than one comma-separated signature. Treat the delivery as valid if any of them matches — that is what makes rotation possible without downtime.
  5. Reject anything that does not verify with 401, and do not process it.

Also check the timestamp. A delivery whose X-Revenexx-Timestamp is far from your own clock — more than a few minutes either way — should be refused, which prevents an old captured delivery being replayed at you later. That check is also why a receiver with a drifting clock rejects perfectly good deliveries; see Common API problems.

5. Rules for the receiver

Insist on these in writing, because you usually do not control the code:

  • Answer fast, process later. Accept the body, return 2xx, do the ERP work afterwards. A receiver that does five seconds of work before answering will time out under load and cause retries that make the load worse.
  • Deduplicate on X-Revenexx-Id. Delivery is at-least-once, so the same event will arrive twice eventually. A repeat must be a no-op.
  • Do not depend on arrival order. Two events a second apart can arrive in either order. If sequence matters, read time from the envelope.
  • Ignore fields you do not recognise. New optional fields appear inside a topic without notice; a receiver that fails on them is a receiver that breaks on an ordinary Tuesday.

6. Send a test delivery and check the log

Trigger a test delivery and confirm the receiver answers 2xx. Then open the webhook's Delivery attempts — each row shows the Event, the attempt number (Try), the Status, the HTTP code and When.

What to look for:

  • The first attempt succeeded. Retries on the very first delivery mean the receiver is slow, and it will be slower on your busiest day.
  • The response time is well inside your receiver's timeout.
  • A deliberately corrupted signature is rejected with 401. Test this. An endpoint that accepts a bad signature is worse than no signature at all, because everyone believes it is protected.

Inbound: the other direction

An external system can also push into the platform. Inbound endpoints have their own address of the form https://webhooks.revenexx.com/in/<endpoint-id>, where the id is long and unguessable, and are managed under Webhooks › Inbound.

Three responses to expect, and to tell the sender about:

CodeMeans
401Signature verification failed
404No such endpoint id
410 GoneThe endpoint exists but has been disabled

Unlike outbound destinations, an inbound endpoint is not disabled automatically when deliveries fail — punishing the sender for a problem on your side would be the wrong behaviour.

What to check

  • A real event produced a delivery, and the receiver processed it once.
  • The same event replayed did not produce a second order.
  • A bad signature is rejected.
  • The delivery log is clean — no growing tail of retries.

Next