Set up webhooks
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.
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:
| Form | Example | Used by |
|---|---|---|
resource.action | order.created | Core platform resources |
vendor.app.entity.action | revenexx.baseline.order.created | Namespaced 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:
| Field | What to put in it |
|---|---|
| Name | The receiving system — ERP order sync, not hook1 |
| Endpoint URL | An https:// address on a system — https://erp.example/hooks/orders |
| Topics | The specific topics. There is an All topics (*) option; resist it |
| Signing secret ref | The 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.
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"
}
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.
| Aspect | Value |
|---|---|
| Algorithm | HMAC-SHA256 |
| Encoding | Hexadecimal |
| Signed content | The raw request body, exactly as received |
| Header | X-Revenexx-Signature: v0=<hex> |
| Secret | The per-endpoint signing secret, of the form whsec_… |
The receiver's job, in order:
- 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".
- Compute HMAC-SHA256 over those bytes with the signing secret.
- Compare in constant time against the hex value after
v0=. A plain string comparison leaks timing information; every language has a constant-time compare. - 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.
- 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
timefrom 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:
| Code | Means |
|---|---|
401 | Signature verification failed |
404 | No such endpoint id |
410 Gone | The 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
- Test an integration end to end — replay, idempotency and the go-live checklist.
- Monitor your integrations — watching deliveries once they are live.
Create an API key
Settings › API Keys — name it for the integration, scope it to exactly what it needs, hand the secret over safely, and test it before anyone builds against it.
SFTP and file exchange
For the ERP that only speaks CSV: provisioning an SFTP account, folder conventions that survive four years, and wiring a dropped file to a scheduled import.