Get new orders instantly with webhooks | Zepio Help Centre
Zepio

How can we help?

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

  1. Go to Settings › Developer API and click Webhooks.
  2. Click Add endpoint.
  3. Enter your URL, for example https://erp.example.com/zepio/webhooks.
  4. Tick the events you want.
  5. Click Add endpoint, then copy the signing secret that is shown. Your code needs it to check that each call really came from Zepio.
  6. Click Send test. Zepio sends a ping event 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.x or 10.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.status and data.previous_status are the change this event is about. For order.updated, previous_status is null and status is the order's status.
  • data.order is read when the event is sent. If the order has moved on again since, its order_status is 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-Id values 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 own X-Zepio-Webhook-Id: one cancellation arrives as both order.status_changed and order.cancelled, and an order edited twice sends two order.updated events. 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's updated_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"
  1. Save the time you start each check.
  2. Ask for orders with updated_after set to the start time of your previous check.
  3. Handle each order the same way you handle a webhook. Orders you already have just update in place.
  4. 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.

Did this answer your question?

Book a call

Tell us about your business and pick a time. We will confirm on WhatsApp, then call you.

Outside India? Change +91 to your country code.

Your business
Pick a day
Pick a time (India time)

Choose a day first.