Guide to Emporix Webhooks: Features and Best Practices
Emporix webhooks tell your systems what is happening in an Emporix commerce tenant: an order is placed or cancelled, a customer signs up, a product or price changes, a cart is closed. If you run B2B commerce on Emporix, webhooks are how those changes reach your ERP, order management, search index and CRM without polling the Commerce Engine API.
This guide covers the three delivery strategies Emporix offers, how the default Svix strategy signs requests, the event types and what their bodies contain, the gaps in Emporix's documentation that affect a receiver, and the best practices for running one in production.
What are Emporix webhooks?
Emporix's Webhook Service publishes Commerce Engine events and hands them to a delivery strategy you choose in the Management Dashboard. There are three strategies, and only one can be active at a time:
- Shared account - Svix is the default. Emporix delivers through its own Svix account, and you manage endpoints in a Svix dashboard that Emporix opens for you.
- Licensed account - Svix connects your own Svix subscription by API key. Delivery works the same way, and the monthly limit follows your Svix plan.
- Other - HTTP posts events directly to URLs you configure in the Management Dashboard, with an optional HMAC secret.
On both Svix strategies, Svix signs every request. Each delivery carries svix-id, svix-timestamp and svix-signature headers, and the signature is an HMAC-SHA256 of the message ID, the timestamp and the raw body, keyed with the endpoint's signing secret. Emporix's own documentation names Svix but never shows the headers or the secret, so the details in this guide come from Svix's documentation of its scheme.
The HTTP strategy signs differently. When a secret is set, it sends an emporix-event-signature header holding a base64 HMAC-SHA256 of the payload after Emporix re-serialises it with every key sorted alphabetically. That is not the raw body, so a receiver has to rebuild the canonical JSON before checking it. Hookdeck's Emporix source type supports the two Svix strategies only.
Emporix webhook features
| Feature | Details |
|---|---|
| Configuration | Administration > Webhooks in the Management Dashboard to choose a strategy and subscribe to events. Endpoints are added in the Svix dashboard on the Svix strategies |
| Strategies | Shared account - Svix (default), Licensed account - Svix, Other - HTTP. One active at a time |
| Signature headers (Svix) | svix-id, svix-timestamp, svix-signature |
| Scheme (Svix) | HMAC-SHA256, base64, sent as one or more space-separated v1,<signature> entries |
| Signed content (Svix) | {svix-id}.{svix-timestamp}.{raw body} |
| Secret (Svix) | One per endpoint, shown in the Svix dashboard, in Svix's whsec_ format |
| HTTP strategy | Optional emporix-event-signature, a base64 HMAC-SHA256 over alphabetically sorted JSON, plus emporix-event-publish-time |
| Event type in the request | None documented. Route by endpoint |
| Volume limit | 5000 events per tenant per month on Svix-Shared. Beyond that, nothing is sent until the first of the next month |
| Retries | Svix's schedule on the Svix strategies. None documented for HTTP |
| IP allowlist | None published |
Common events
Emporix documents 81 event types across 16 services. Each is a dotted string, and you switch each one on separately.
| Service | Events |
|---|---|
| Order | order.created, order.updated, order.cancelled, order.status-changed, order.deleted |
| Customer | customer.created, customer.updated, customer.deleted, customer.sign-up, customer.logged.in, customer.opt-in, customer.password-reset, customer.refresh-token, customer.email-change, customer.email-change-request |
| Cart | cart.created, cart.updated, cart.merged, cart.closed, cart.item-added, cart.item-updated, cart.item-deleted |
| Product and catalog | product.created, product.updated, product.deleted, catalog.*, category.*, assignment.created, assignment.deleted |
| Pricing | price.*, price-model.*, price-list.*, price-list-price.* (each created, updated and deleted) |
| Quotes and returns | quote.created, quote.updated, quote.deleted, return.created, return.updated, return.deleted |
| Availability | availability.created, availability.updated, availability.deleted |
| B2B accounts | client-management.legalentity-*, client-management.location-*, client-management.user-assigned-to-legalentity, customer-segment.* |
| Other | index-item.updated, index-item.deleted, schema.custom-instance-*, site.site-*, vendor.vendor-* |
Two details matter for routing. First, the index events have two names in Emporix's own docs: the Management Dashboard page calls one index.item-updated, while the event reference uses index-item.updated. Check the Event catalog in the Svix dashboard for the string your tenant uses. Second, index-item.updated fires once per site that a product's prices are attached to, so one product change can produce several events.
The body of each event is the entity itself (an order, a customer, a product), limited to the attributes you selected when you subscribed. Emporix documents no envelope and no type or eventType field, and Svix does not add the event type to the payload. A customer.created body might carry id, customerNumber, contactEmail, firstName and lastName, and nothing in it says it is a customer.created event rather than customer.updated. Emporix publishes field lists for each event rather than example bodies, so the first real delivery is your best reference for the exact shape.
Inspect Emporix webhooks as they arrive. Point a Svix endpoint at a Hookdeck Inspect URL to inspect and replay real deliveries, with no account or setup required.
Setting up Emporix webhooks
- In the Management Dashboard, go to Administration > Webhooks, choose Shared account - Svix (or Licensed account - Svix with your Svix API key), and confirm with Active.
- Choose Open Svix Dashboard. Emporix redirects you to Svix, where your tenant is the Svix application.
- In Svix, go to Endpoints > Add endpoint, enter your HTTPS URL, choose the event types, and click Create.
- Open the endpoint and copy its Signing Secret, which starts with
whsec_. Store it server-side, for example asEMPORIX_WEBHOOK_SECRET. Each endpoint has its own secret. - Back in Administration > Webhooks, expand each service, switch on the events you want, and use the edit icon to choose which attributes appear in the payload.
Because the body carries no event type, give each event type its own endpoint and encode the type in the URL, for example https://example.com/webhooks/emporix?eventType=order.created. Your handler can then read the type from the query string or path.
To get the Svix dashboard link programmatically, GET https://api.emporix.io/webhook/{tenant}/dashboard-access returns a Svix magic login link.
If you receive Emporix webhooks through Hookdeck, use one of the Svix strategies, not the HTTP strategy, and paste the endpoint's signing secret from the Svix dashboard into the Hookdeck source. Hookdeck cannot verify the HTTP strategy's emporix-event-signature.
Securing Emporix webhooks
Svix's scheme is the same one Standard Webhooks describes. Strip whsec_ from the secret and base64-decode the rest to get the key. Build {svix-id}.{svix-timestamp}.{raw body}, compute an HMAC-SHA256 with that key, base64-encode it, and compare it in constant time with every v1, entry in svix-signature. Several entries appear while a secret is being rotated, and any match passes.
const crypto = require('crypto');
const express = require('express');
function verifyEmporixSignature(rawBody, headers, secret) {
const id = headers['svix-id'];
const timestamp = headers['svix-timestamp'];
const signatureHeader = headers['svix-signature'];
if (!id || !timestamp || !signatureHeader || !secret) return false;
// The key is the base64-decoded part after "whsec_", not the string itself.
const key = Buffer.from(secret.replace(/^whsec_/, ''), 'base64');
const expected = crypto
.createHmac('sha256', key)
.update(`${id}.${timestamp}.`)
.update(rawBody)
.digest('base64');
const computed = Buffer.from(expected, 'utf8');
return signatureHeader.split(' ').some((entry) => {
const [version, signature] = entry.split(',');
if (version !== 'v1' || !signature) return false;
const received = Buffer.from(signature, 'utf8');
return received.length === computed.length && crypto.timingSafeEqual(received, computed);
});
}
const app = express();
app.post('/webhooks/emporix', express.raw({ type: '*/*' }), (req, res) => {
if (!verifyEmporixSignature(req.body, req.headers, process.env.EMPORIX_WEBHOOK_SECRET)) {
return res.sendStatus(401);
}
// Optional: reject if svix-timestamp is too far from now. Leave this off
// behind Hookdeck, because retries and replays resend the original timestamp.
const eventType = req.query.eventType; // set per endpoint in the Svix dashboard
const entity = JSON.parse(req.body.toString('utf8'));
// Deduplicate on req.headers['svix-id'], persist, then process asynchronously.
res.sendStatus(200);
});
The same check in Python:
import base64
import hashlib
import hmac
def verify_emporix_signature(raw_body: bytes, svix_id, svix_timestamp, svix_signature, secret: str) -> bool:
if not (svix_id and svix_timestamp and svix_signature and secret):
return False
key = base64.b64decode(secret.removeprefix("whsec_"))
signed = f"{svix_id}.{svix_timestamp}.".encode() + raw_body
expected = base64.b64encode(hmac.new(key, signed, hashlib.sha256).digest())
for entry in svix_signature.split(" "):
version, _, signature = entry.partition(",")
if version == "v1" and hmac.compare_digest(signature.encode(), expected):
return True
return False
You can also use the official svix library (new Webhook(secret).verify(rawBody, headers)) or the standardwebhooks library with the svix-* values passed as webhook-*. Both always reject a svix-timestamp more than 5 minutes from the current time, and the window cannot be turned off. Hookdeck retries and replays resend the original headers, so any replay of an older Emporix event through Hookdeck fails that check even though the signature is valid. The manual verifier above has no timestamp window, which is why the Emporix skill uses it.
Emporix never documents the header names its Svix deliveries use. Svix's defaults are svix-*, and Svix also supports webhook-* names. Log the headers of your first delivery to confirm which you receive. Hookdeck's Emporix source accepts either.
Make Emporix webhooks production-ready. Hookdeck Event Gateway verifies the Svix signature with your endpoint's
whsec_secret, keeps every order, customer and product event in a durable queue, and lets you replay any of them without a timestamp window getting in the way.
Emporix webhook limitations and pain points
No event type in the request
The Problem: A single endpoint that receives several event types cannot tell a cart.created from a cart.updated, because both bodies are a cart.
Why It Happens: Emporix documents each event's payload as the entity alone, with no envelope. Svix does not inject the event type into the payload, and no event-type header is documented.
Workarounds:
- Create one Svix endpoint per event type and put the type in the URL, as a path segment or query parameter.
- Log the full headers and body of the first delivery of each type, in case a type carrier turns up that the docs do not mention.
How Hookdeck Can Help: Point every Svix endpoint at one Hookdeck source with a different query string, then use filters on the query or path to route each event type to its own destination.
The HTTP strategy signs re-serialised JSON
The Problem: A tenant on the HTTP strategy finds that a standard HMAC of the raw body never matches emporix-event-signature.
Why It Happens: Emporix signs a canonical JSON serialisation, with keys sorted alphabetically at every level and integers written as integers, not the bytes it sent. It also signs no timestamp, so there is no replay protection.
Workarounds:
- Use a Svix strategy where you can, which signs the raw body and a timestamp.
- If you must stay on HTTP, rebuild the canonical JSON with a stable stringifier before computing the HMAC, as Emporix's own sample does.
How Hookdeck Can Help: Hookdeck's Emporix source verifies the Svix strategies. For an HTTP-strategy tenant, Hookdeck can still receive, queue and replay the events, and the canonical check stays in your code.
A monthly cap on the default strategy
The Problem: On Svix-Shared, events stop arriving partway through a month and nothing errors.
Why It Happens: Emporix limits Svix-Shared to 5000 events per tenant per month. Once you exceed it, requests are not sent until the limit resets on the first of the month. Svix dashboard test events count too.
Workarounds:
- Watch Webhook usage under Administration > Webhooks, and alert well before 5000.
- Subscribe only to the events and attributes you need.
index-item.updated, which fires once per site, can use up the allowance quickly. - Move to the licensed Svix strategy, or contact Emporix sales, if your volume is higher.
How Hookdeck Can Help: Hookdeck's request log gives you a running count of what Emporix sent to each source, so you can see the volume climbing before deliveries stop.
Library timestamp checks break replays
The Problem: Events verify on first delivery, but retries and replays that arrive later fail with a timestamp error.
Why It Happens: The svix and standardwebhooks libraries reject any svix-timestamp more than 5 minutes old, and a retry or replay keeps the original timestamp.
Workarounds:
- Verify manually with no timestamp window, as shown above, and rely on deduplication by
svix-idfor replay protection. - If you want a window, make it wider than your longest retry or replay delay.
How Hookdeck Can Help: Hookdeck verifies the signature when the request arrives and records the result, so you can replay a stored event to your destination later without it failing on age.
Best practices
Verify against the raw body
The signature covers the exact bytes Svix sent. In Express, mount express.raw({ type: '*/*' }) on the route. In Next.js, use await request.text(). In FastAPI, read await request.body(). Parse only after verification passes.
Decode the secret
The HMAC key is the base64-decoded bytes after whsec_. Using the whole string as the key produces a valid-looking signature that never matches. Each Svix endpoint has its own secret, so a secret copied from another endpoint fails too.
Deduplicate on svix-id
Svix keeps svix-id the same across retries of one message, which makes it the natural idempotency key. Store it before processing. See our guide to webhook idempotency.
Acknowledge fast and process asynchronously
Return a 2xx as soon as the request is verified and stored, then do the ERP or search update in the background. Svix's documentation treats a 2xx within a reasonable time (30 seconds with Svix) as success, and retries anything else on its schedule of 5 seconds, 5 minutes, 30 minutes, 2 hours, 5 hours, 10 hours and 10 hours. Emporix does not state whether its Svix account changes those defaults. See why to process webhooks asynchronously.
Trim payloads at the source
You choose which attributes each event carries when you subscribe. Subscribe only to the fields you use, and fetch the full entity from the Emporix API when you need more. Smaller payloads are easier to store and replay.
Conclusion
Emporix delivers webhooks through Svix by default, so verification follows Svix's scheme: an HMAC-SHA256 over {svix-id}.{svix-timestamp}.{raw body}, keyed with the decoded whsec_ secret from the Svix dashboard. Because the body carries no event type, register an endpoint per event type, deduplicate on svix-id, keep an eye on the 5000-event monthly cap, and avoid libraries whose fixed timestamp window rejects late retries.
Hookdeck Event Gateway checks every Svix signature from Emporix, routes each event type by URL, and holds events until your service has processed them, so an outage on your side does not cost you orders.
Get started with Hookdeck for free and handle Emporix webhooks reliably in minutes.