Webhooks
Webhooks are coming soon. This page documents the planned design so you can prepare your integration. We will announce availability on the Zivvy blog and in the dashboard.
Webhooks let your application receive real-time HTTP notifications when events happen in your Zivvy account — an order is submitted, an invoice is paid, stock levels change. Instead of polling the API, your server gets a POST request the moment something happens.
How webhooks work
- You register a webhook endpoint URL in the Zivvy dashboard.
- You select which event types to subscribe to.
- When a matching event occurs, Zivvy sends an HTTP POST request to your endpoint with a JSON payload describing the event.
- Your endpoint responds with a
200status to acknowledge receipt.
Registering a webhook
In the dashboard, go to Settings → Webhooks and click Create Webhook:
- URL — The HTTPS endpoint on your server that will receive events. Must be publicly accessible and use HTTPS.
- Events — Select one or more event types to subscribe to, or choose "All events".
- Secret — A signing secret is generated automatically. Use it to verify that incoming requests are genuinely from Zivvy.
Event types
| Event | Description |
|---|---|
item.created | A new item was created |
item.updated | An item was modified |
item.deleted | An item was deleted |
customer.created | A new customer was created |
customer.updated | A customer record was modified |
order.submitted | A sales order was submitted |
order.cancelled | A sales order was cancelled |
invoice.created | A new invoice was created |
invoice.paid | An invoice was marked as paid |
invoice.overdue | An invoice became overdue |
payment.received | A payment was received |
stock.updated | Stock levels changed for one or more items |
stock.reorder | Stock fell below the reorder level |
Payload format
Every webhook delivery is an HTTP POST with a JSON body:
json{
"id": "evt_a1b2c3d4e5f6",
"type": "order.submitted",
"created_at": "2024-03-15T14:30:00Z",
"data": {
"name": "SO-00042",
"customer": "CUST-001",
"customer_name": "Acme Corp",
"grand_total": 1249.50,
"currency": "USD",
"items": [
{
"item_code": "IT-00001",
"item_name": "Standing Desk",
"qty": 2,
"rate": 499.00,
"amount": 998.00
},
{
"item_code": "IT-00015",
"item_name": "Monitor Arm",
"qty": 1,
"rate": 251.50,
"amount": 251.50
}
],
"status": "submitted",
"submitted_at": "2024-03-15T14:30:00Z"
}
}
| Field | Type | Description |
|---|---|---|
id | string | Unique event identifier (use for deduplication) |
type | string | The event type (e.g., order.submitted) |
created_at | string | ISO 8601 timestamp of when the event occurred |
data | object | The resource data at the time of the event |
Signature verification
Every webhook request includes a signature in the X-Zivvy-Signature header. Verify this signature to ensure the request came from Zivvy and was not tampered with.
The signature is an HMAC-SHA256 hash of the raw request body, using your webhook secret as the key:
Python
pythonimport hmac
import hashlib
def verify_webhook(payload_body, signature_header, secret):
expected = hmac.new(
secret.encode("utf-8"),
payload_body,
hashlib.sha256
).hexdigest()
return hmac.compare_digest(
f"sha256={expected}",
signature_header
)
JavaScript (Node.js)
javascriptconst crypto = require("crypto");
function verifyWebhook(payloadBody, signatureHeader, secret) {
const expected = crypto
.createHmac("sha256", secret)
.update(payloadBody)
.digest("hex");
return signatureHeader === `sha256=${expected}`;
}
Express middleware example
javascriptapp.post("/webhooks/zivvy", express.raw({ type: "application/json" }), (req, res) => {
const signature = req.headers["x-zivvy-signature"];
const secret = process.env.ZIVVY_WEBHOOK_SECRET;
if (!verifyWebhook(req.body, signature, secret)) {
return res.status(401).send("Invalid signature");
}
const event = JSON.parse(req.body);
switch (event.type) {
case "order.submitted":
handleNewOrder(event.data);
break;
case "invoice.paid":
handleInvoicePaid(event.data);
break;
default:
console.log(`Unhandled event type: ${event.type}`);
}
res.status(200).send("OK");
});
Always verify the signature before processing a webhook. Never trust the payload without verification.
Retry policy
If your endpoint does not respond with a 2xx status within 10 seconds, Zivvy retries the delivery with exponential backoff:
| Attempt | Delay after previous attempt |
|---|---|
| 1st retry | 1 minute |
| 2nd retry | 5 minutes |
| 3rd retry | 30 minutes |
| 4th retry | 2 hours |
| 5th retry | 12 hours |
After 5 failed retries, the delivery is marked as failed. You can view and manually retry failed deliveries in the dashboard under Settings → Webhooks → Delivery Log.
If your endpoint fails consistently for 3 consecutive days, the webhook is automatically disabled and you will receive an email notification.
Handling webhooks correctly
- Respond quickly. Return a
200status as soon as you receive the request. Process the event asynchronously (e.g., add it to a queue) rather than doing heavy work inline. - Handle duplicates. Use the event
idfield to deduplicate. In rare cases (network issues, retries), you may receive the same event more than once. - Verify signatures. Always check
X-Zivvy-Signaturebefore processing. - Use HTTPS. Webhook endpoints must use HTTPS. Plain HTTP endpoints will be rejected.
- Handle unknown events gracefully. New event types may be added in the future. Your handler should not break if it receives an event type it does not recognize.
Testing webhooks
During development, you can use tools like ngrok or localtunnel to expose your local server to the internet and test webhook deliveries.
The dashboard also includes a Send Test Event button for each webhook, which delivers a sample payload to your endpoint so you can verify your setup without waiting for a real event.