Get new orders instantly with webhooks
Zepio sends each new order, every status change and every edit to your URL the moment it happens, so your ERP, billing or delivery system does not have to keep asking for orders.
Updated 19 Oct 2026 7 min read
On this page
A webhook is a URL on your own server that Zepio calls when something happens in your store. Instead of your code asking GET /orders every minute, Zepio tells it the moment an order comes in or changes. Your integration gets orders faster and makes far fewer requests.
Where: Admin › Settings › Developer API › Webhooks
Events
| Event | When it is sent |
|---|---|
order.created |
A new order is placed. For online payments, once the payment succeeds. Orders that are never paid are not sent. |
order.status_changed |
An order moves to another status, for example placed to processing, or to cancelled. |
order.cancelled |
An order is cancelled. It is also sent as order.status_changed, so subscribe to one or the other, not both, unless you want two calls. |
order.updated |
An order changes but keeps its status: items edited by you or a picker packer (so the totals change), a new delivery address, a new delivery date, or new tracking details. data.change says which. |
order.updated is not sent for orders that were never announced (unpaid) or that are cancelled. When tracking is saved in the same step that moves the order on, for example marking it shipped with a tracking link through the API, you get one order.status_changed with the new tracking in it, not an order.updated as well.
Each endpoint chooses which events it gets.
Add an endpoint
- Go to Settings › Developer API and click Webhooks.
- Click Add endpoint.
- Enter your URL, for example
https://erp.example.com/zepio/webhooks. - Tick the events you want.
- Click Add endpoint, then copy the signing secret that is shown. Your code needs it to check that each call really came from Zepio.
- Click Send test. Zepio sends a
pingevent and tells you what your server answered.
A store can have up to 5 endpoints.
URL rules
- It must start with
https://. - It must be reachable from the internet. Addresses on a private or local network (such as
localhost,192.168.x.xor10.x.x.x) are refused. - Zepio does not follow redirects. Use the final URL.
What Zepio sends
Each event is a POST with a JSON body.
Headers
| Header | What it holds |
|---|---|
X-Zepio-Event |
The event, for example order.created |
X-Zepio-Signature |
HMAC-SHA256 of the raw body, made with your signing secret, as lowercase hex |
X-Zepio-Webhook-Id |
A unique ID for this webhook. It stays the same when Zepio retries it, so you can use it to spot duplicates. |
X-Zepio-Attempt |
1 for the first try, 2 for the first retry, and so on |
Content-Type |
application/json |
Body
{
"event": "order.created",
"event_id": "evt_01k7rdy0mf6x0ytm4b8kcp4fbn",
"created_at": "2026-10-17T09:06:34+00:00",
"data": {
"order_id": 1024,
"status": "placed",
"previous_status": "pending",
"order": {
"id": 1024,
"order_number": "OD-1024",
"order_status": "placed",
"total": 850,
"payment_gateway": "cod",
"user": { "id": 12, "name": "Asha Rao", "phone": "9876543210" },
"items": [ ... ],
"address": { ... }
}
}
}
What changed, for order.updated
order.updated adds data.change:
change |
What happened |
|---|---|
contents |
Items were added, removed or changed, so the totals may have changed too |
delivery_address |
The delivery address was changed |
delivery_date |
The delivery date was set, changed or cleared |
tracking |
The tracking message or tracking link was changed |
{
"event": "order.updated",
"event_id": "evt_01k7rf3b2s1m8q7v0yq3x6h5ze",
"created_at": "2026-10-17T10:12:05+00:00",
"data": {
"order_id": 1024,
"status": "processing",
"previous_status": null,
"change": "contents",
"order": { "id": 1024, "order_number": "OD-1024", "total": 790, "items": [ ... ] }
}
}
In every case data.order is the whole order as it is now, so replace your copy with it rather than trying to apply a difference.
data.order is the same order you get from GET /orders/{id} in the Orders API, so one piece of code can read both.
data.statusanddata.previous_statusare the change this event is about. Fororder.updated,previous_statusisnullandstatusis the order's status.data.orderis read when the event is sent. If the order has moved on again since, itsorder_statusis the newer one.
Check the signature
Before you trust an event, compute the HMAC-SHA256 of the raw request body with your signing secret and compare it with X-Zepio-Signature. Use the body exactly as it arrived. If you parse the JSON and encode it again, the signature will not match.
PHP
$body = file_get_contents('php://input');
$expected = hash_hmac('sha256', $body, 'whsec_your_secret');
if (!hash_equals($expected, $_SERVER['HTTP_X_ZEPIO_SIGNATURE'] ?? '')) {
http_response_code(401);
exit;
}
$event = json_decode($body, true);
Node.js (Express)
const crypto = require("crypto");
app.post("/zepio/webhooks", express.raw({ type: "application/json" }), (req, res) => {
const expected = crypto.createHmac("sha256", process.env.ZEPIO_WEBHOOK_SECRET).update(req.body).digest("hex");
const received = req.get("X-Zepio-Signature") || "";
if (expected.length !== received.length || !crypto.timingSafeEqual(Buffer.from(expected), Buffer.from(received))) {
return res.sendStatus(401);
}
const event = JSON.parse(req.body);
res.sendStatus(200);
// handle the event after replying
});
Python
import hashlib, hmac
expected = hmac.new(SECRET.encode(), request.get_data(), hashlib.sha256).hexdigest()
if not hmac.compare_digest(expected, request.headers.get("X-Zepio-Signature", "")):
abort(401)
Reply quickly
Answer with any 2xx status within 10 seconds. Anything else, or no answer in time, counts as a failure.
If handling an order takes longer, for example writing it to your ERP, save the event, reply 200 straight away and do the work afterwards.
Retries and switched-off endpoints
When a call fails, Zepio tries again with the same body and the same X-Zepio-Webhook-Id:
| Attempt | When |
|---|---|
| 1 | Straight away |
| 2 | 1 minute later |
| 3 | 5 minutes after that |
| 4 | 30 minutes after that |
| 5 | 2 hours after that |
So a server that is down for up to about 2.5 hours misses nothing.
After 20 failed attempts in a row, with no success in between, Zepio switches the endpoint off and shows the reason on the Webhooks page. Fix your server, click Send test, then click Switch back on. Events that happened while it was off are not sent again. Fetch them with GET /orders?updated_after= and the time of the last event you received, as in Catch anything you missed.
Handle duplicates and order
- The same event can arrive twice, for example when your server saved it but the reply was lost. Keep the
X-Zepio-Webhook-Idvalues you have handled and skip ones you have seen. - Make handling the same order twice safe. Save orders under their order
id, so a second event for the same order updates your record instead of creating a copy. Different events about the same order each have their ownX-Zepio-Webhook-Id: one cancellation arrives as bothorder.status_changedandorder.cancelled, and an order edited twice sends twoorder.updatedevents. Each one carries the full order, so handling it is always "save this order". - Events can arrive out of order, especially after a retry. Compare
created_at, or the order'supdated_at, with what you already have before you overwrite anything.
Catch anything you missed
Retries cover a server that is down for a while, but not every way an event can get lost: your server could be down longer than 2.5 hours, a bug could drop an event after replying 200, or a deploy could clear your own queue.
So keep webhooks as your main feed, and once an hour ask the Orders API for anything that changed since your last check:
curl -X GET "https://yourwebsite.com/api/developer/v1/orders?updated_after=2026-10-17T09:00:00%2B05:30&per_page=100" \
-H "Authorization: Bearer YOUR_API_KEY" \
-H "Accept: application/json"
- Save the time you start each check.
- Ask for orders with
updated_afterset to the start time of your previous check. - Handle each order the same way you handle a webhook. Orders you already have just update in place.
- If the reply has a next page, follow its cursor until there are no more pages.
If orders are urgent for you, every 15 minutes is fine. Hourly is about 24 requests a day, every 15 minutes about 100, both far below the 60 a minute your keys allow. The key needs the Read orders ability.
See what was sent
Click Deliveries on an endpoint to see the last 50 events: the event, the result, how many attempts it took, your server's status code and, when you open one, the exact body sent and what your server replied. Deliveries are kept for 30 days.
Keep it safe
- Always check the signature. Anyone can send a request to your URL; only Zepio knows the secret.
- If the secret may have leaked, click Secret › New secret. The old one stops working at once, so update your code at the same time.
- Delete endpoints you no longer use.