Real-time events from your workspace
Subscribe once, receive HMAC-signed POSTs for every meaningful change across CRM, sales, billing, banking, stock, HR, projects, and manufacturing.
100+ event types across 40+ resources. At-least-once delivery with exponential retry. Available on Free and every paid plan — register your first endpoint in the dashboard.
Every event below is deliverable today. Register endpoints in Settings → Developer or via POST /v1/webhooks. Every delivery is signed X-Zivvy-Signature: sha256=….
Payload envelope
Every delivery wraps the resource payload in a stable envelope. The envelope shape does not change per event — only the data body does.
{
"event": "sales-invoices.paid",
"resource": "sales-invoices",
"delivery_id": "whd_2026_07_25_a91f24",
"timestamp": "2026-07-25T14:02:11.428Z",
"data": {
"id": "SINV-0801",
"customer": "CUST-0012",
"grand_total": 1250.00,
"outstanding_amount": 0,
"paid_at": "2026-07-25T14:02:00Z"
}
}POST /your/endpoint HTTP/1.1
Host: hooks.example.com
Content-Type: application/json
User-Agent: Zivvy-Webhooks/1.0
X-Zivvy-Event: sales-invoices.paid
X-Zivvy-Delivery: whd_2026_07_25_a91f24
X-Zivvy-Timestamp: 1753452131
X-Zivvy-Signature: t=1753452131, v1=6f1c...bd94event- Fully-qualified event name — <resource>.<verb>.
resource- Slug of the resource that triggered the event.
delivery_id- Unique per delivery attempt. Use it for idempotency on your side.
timestamp- ISO 8601, server-side. Included in the signature.
data- The resource payload — same shape as the corresponding GET response.
Event catalog
39+ events across 10 resource groupsSubscribe to specific events, a resource wildcard (sales-orders.*), or all events with *. The machine-readable list is at GET /v1/webhooks/events once outbound delivery is live.
CRM
Pipeline motion — leads and opportunities as they move.
leads.createdA new lead was captured (form, import, or API).
Sample payload(show)
{ "id": "LEAD-0007", "lead_name": "Jane Smith", "email_id": "jane@acme.com", "status": "Open" }leads.updatedAny field on the lead changed.
Sample payload(show)
{ "id": "LEAD-0007", "status": "Contacted" }leads.convertedLead was converted into a customer + opportunity.
Sample payload(show)
{ "id": "LEAD-0007", "customer_id": "CUST-0012", "opportunity_id": "OPP-0031" }opportunities.wonAn opportunity was marked Won.
Sample payload(show)
{ "id": "OPP-0031", "opportunity_amount": 48250, "currency": "USD" }opportunities.lostAn opportunity was marked Lost.
Sample payload(show)
{ "id": "OPP-0031", "lost_reason": "Price" }
Sales
Quotes, orders, and shipments moving through the pipeline.
quotations.submittedQuotation was submitted (locked, sent to buyer).
Sample payload(show)
{ "id": "QTN-0102", "customer": "CUST-0012", "grand_total": 1250.00 }sales-orders.submittedSales order accepted — start fulfillment.
Sample payload(show)
{ "id": "SO-0451", "customer": "CUST-0012", "delivery_date": "2026-08-04" }sales-orders.cancelledSales order was cancelled after submission.
Sample payload(show)
{ "id": "SO-0451", "cancelled_at": "2026-07-25T10:14:00Z" }delivery-notes.submittedGoods left the warehouse against a sales order.
Sample payload(show)
{ "id": "DN-0223", "sales_order": "SO-0451", "posting_date": "2026-07-25" }
Billing & Invoicing
Invoicing lifecycle — issued, paid, or credited.
sales-invoices.submittedInvoice was issued to the customer.
Sample payload(show)
{ "id": "SINV-0801", "customer": "CUST-0012", "grand_total": 1250.00, "due_date": "2026-08-24" }sales-invoices.paidInvoice was fully settled.
Sample payload(show)
{ "id": "SINV-0801", "outstanding_amount": 0, "paid_at": "2026-08-11T14:02:00Z" }purchase-invoices.submittedSupplier bill was recorded.
Sample payload(show)
{ "id": "PINV-0334", "supplier": "SUP-0004" }payment-entries.submittedA payment was posted against one or more invoices.
Sample payload(show)
{ "id": "PE-0912", "paid_amount": 1250.00, "references": [{ "sales_invoice": "SINV-0801" }] }payment-requests.paidA hosted payment request was completed by the payer.
Sample payload(show)
{ "id": "PR-0087", "gateway": "stripe", "amount": 1250.00 }
Purchasing
Purchase orders and inbound receipts.
purchase-orders.submittedPO was issued to the supplier.
Sample payload(show)
{ "id": "PO-0119", "supplier": "SUP-0004" }purchase-receipts.submittedGoods received against a PO — stock has increased.
Sample payload(show)
{ "id": "PREC-0281", "purchase_order": "PO-0119" }
Stock
Movement, transfers, and inventory events.
items.createdA new item was added to the catalog.
Sample payload(show)
{ "id": "ITM-0455", "item_code": "WIDGET-BLK-L", "item_group": "Widgets" }stock-entries.submittedStock was moved, issued, or received internally.
Sample payload(show)
{ "id": "SE-0621", "stock_entry_type": "Material Transfer" }pick-lists.submittedA pick list was generated for warehouse fulfillment.
Sample payload(show)
{ "id": "PL-0034", "purpose": "Delivery" }shipments.submittedA shipment was booked with a carrier.
Sample payload(show)
{ "id": "SH-0088", "delivery_note": "DN-0223" }
Banking
Bank feeds, reconciliation, and balance changes.
bank-transactions.createdA new bank transaction landed in a feed.
Sample payload(show)
{ "id": "BT-2026-0000451", "bank_account": "BA-DE-DKB-01", "amount": 1250.00, "date": "2026-07-25" }bank-transactions.matchedA bank transaction was matched to a payment or invoice.
Sample payload(show)
{ "id": "BT-2026-0000451", "matched_to": "PE-0912" }bank-transactions.reconciledA bank transaction was reconciled and closed.
Sample payload(show)
{ "id": "BT-2026-0000451" }bank-accounts.balance_updatedThe running balance on a bank account changed.
Sample payload(show)
{ "id": "BA-DE-DKB-01", "balance": 48250.00, "currency": "EUR" }bank-statement-imports.completedA statement file finished importing.
Sample payload(show)
{ "id": "BSI-0011", "imported_rows": 84 }
HR & People
Joiners, leavers, leave, expenses, and payroll.
employees.createdA new employee record was added.
Sample payload(show)
{ "id": "EMP-0033", "employee_name": "Priya Patel" }leave-applications.approvedA leave request was approved.
Sample payload(show)
{ "id": "LA-0142", "employee": "EMP-0033", "from_date": "2026-08-05", "to_date": "2026-08-09" }attendance.submittedAttendance was recorded for a day.
Sample payload(show)
{ "id": "ATT-2026-07-25-0033", "status": "Present" }expense-claims.approvedAn expense claim was approved for reimbursement.
Sample payload(show)
{ "id": "EC-0077", "total_claimed_amount": 142.50 }salary-slips.submittedA payroll slip was finalized for the period.
Sample payload(show)
{ "id": "SAL-2026-07-EMP-0033", "net_pay": 3820.00 }
Projects
Projects, tasks, and time tracking.
projects.completedA project was marked complete.
Sample payload(show)
{ "id": "PROJ-0018", "project_name": "Website refresh" }tasks.completedA task moved to Completed.
Sample payload(show)
{ "id": "TASK-0221", "project": "PROJ-0018" }timesheets.submittedA timesheet was submitted for approval.
Sample payload(show)
{ "id": "TS-0089", "total_hours": 37.5 }
Manufacturing
Production plans, work orders, and BOMs.
boms.submittedA BOM was submitted (active).
Sample payload(show)
{ "id": "BOM-WIDGET-001", "item": "ITM-0455" }work-orders.completedA work order finished production.
Sample payload(show)
{ "id": "WO-0154", "produced_qty": 500 }production-plans.completedA production plan closed out.
Sample payload(show)
{ "id": "PP-0022" }
Support & Assets
Tickets and asset lifecycle.
issues.createdA new support ticket was opened.
Sample payload(show)
{ "id": "ISS-0409", "customer": "CUST-0012", "priority": "High" }issues.resolvedA ticket was marked Resolved.
Sample payload(show)
{ "id": "ISS-0409", "resolution_time_seconds": 18420 }assets.scrappedAn asset was scrapped from the register.
Sample payload(show)
{ "id": "ASSET-0044", "scrap_date": "2026-07-25" }
Signature verification
Every delivery is signed with an HMAC-SHA256 of <timestamp>.<raw-body> using the shared secret you supplied on the subscription. The signature travels in the X-Zivvy-Signature header. Verify against the raw request body — do not parse JSON first.
import crypto from "node:crypto";
import express from "express";
const app = express();
const SECRET = process.env.ZIVVY_WEBHOOK_SECRET!;
// Zivvy signs the raw body. Use express.raw() so req.body stays
// as a Buffer — do NOT run JSON parsing before verification.
app.post(
"/webhooks/zivvy",
express.raw({ type: "application/json" }),
(req, res) => {
const signatureHeader = req.header("X-Zivvy-Signature") || "";
const timestamp = req.header("X-Zivvy-Timestamp") || "";
// Reject payloads older than 5 minutes to blunt replay attacks.
const age = Math.abs(Date.now() / 1000 - Number(timestamp));
if (!Number.isFinite(age) || age > 300) {
return res.status(400).send("stale timestamp");
}
const payload = `${timestamp}.${req.body.toString("utf8")}`;
const expected = crypto
.createHmac("sha256", SECRET)
.update(payload)
.digest("hex");
// "t=…, v1=<hex>" — accept any v1 sig in the header.
const v1 = /v1=([a-f0-9]+)/.exec(signatureHeader)?.[1] ?? "";
const ok =
v1.length === expected.length &&
crypto.timingSafeEqual(Buffer.from(v1), Buffer.from(expected));
if (!ok) return res.status(400).send("bad signature");
const event = JSON.parse(req.body.toString("utf8"));
// Ack fast (2xx within 5s), then process in a background job.
res.status(200).send("ok");
}
);- Compare with a constant-time function (
timingSafeEqual/hmac.compare_digest). - Reject payloads with a timestamp more than 5 minutes off.
- Rotate the secret from the dashboard — both keys stay valid during a 24-hour overlap window.
- Return a 2xx quickly (under 5 seconds), then queue the work.
Delivery guarantees
At-least-once delivery
Every event is delivered at least once. Design idempotently — de-duplicate on delivery_id, which is stable across retries.
Exponential backoff
Non-2xx or timeout (>10s) triggers a retry with jittered exponential backoff — up to 24 hours, 12 attempts.
Ordering is not guaranteed
Two events can arrive out of order. Trust the timestamp on the envelope, not the arrival order.
Delivery log
Every attempt — status code, latency, response body preview, and error — is retained for 30 days. Fetch it any time to debug:
curl https://api.zivvy.xyz/v1/webhooks/whk_01H8.../deliveries \
-H "Authorization: Bearer $ZIVVY_API_KEY"Register a webhook
Management endpoints (POST /v1/webhooks, GET /v1/webhooks, DELETE /v1/webhooks/:id) are already live. Registrations you make today will start receiving events automatically when outbound delivery ships — no re-registration needed.
curl -X POST https://api.zivvy.xyz/v1/webhooks \
-H "Authorization: Bearer $ZIVVY_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"url": "https://hooks.example.com/webhooks/zivvy",
"events": [
"sales-invoices.paid",
"sales-orders.submitted",
"bank-transactions.matched"
],
"secret": "whsec_your_generated_shared_secret",
"label": "Production billing sync"
}'IP allowlist · pending
A stable set of egress IPs will be published at launch so you can pin firewall rules. Until then, rely on the HMAC signature as the primary authenticity check — signature verification is enough on its own, and remains the recommended approach even once IPs are pinned.
Ship your first webhook in under a minute
Register an endpoint, subscribe to the events you care about, and get signed POSTs the moment something happens in your workspace.