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

  1. You register a webhook endpoint URL in the Zivvy dashboard.
  2. You select which event types to subscribe to.
  3. When a matching event occurs, Zivvy sends an HTTP POST request to your endpoint with a JSON payload describing the event.
  4. Your endpoint responds with a 200 status 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

EventDescription
item.createdA new item was created
item.updatedAn item was modified
item.deletedAn item was deleted
customer.createdA new customer was created
customer.updatedA customer record was modified
order.submittedA sales order was submitted
order.cancelledA sales order was cancelled
invoice.createdA new invoice was created
invoice.paidAn invoice was marked as paid
invoice.overdueAn invoice became overdue
payment.receivedA payment was received
stock.updatedStock levels changed for one or more items
stock.reorderStock 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"
  }
}
FieldTypeDescription
idstringUnique event identifier (use for deduplication)
typestringThe event type (e.g., order.submitted)
created_atstringISO 8601 timestamp of when the event occurred
dataobjectThe 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:

AttemptDelay after previous attempt
1st retry1 minute
2nd retry5 minutes
3rd retry30 minutes
4th retry2 hours
5th retry12 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 200 status 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 id field to deduplicate. In rare cases (network issues, retries), you may receive the same event more than once.
  • Verify signatures. Always check X-Zivvy-Signature before 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.