Webhooks
Subscribe a URL to deal, invoice, requirement, and contact events and NeoKivo POSTs a signed payload as they happen. The event catalog, delivery and retry behavior, and what happens when a webhook stops working.
Updated 2026-07-21
A webhook is a URL you register in Settings that NeoKivo calls when something happens in your workspace. It's the fastest way to react to pipeline and post-sale events from outside NeoKivo — no polling required.
The event catalog
| Event | Fires when |
|---|---|
| deal.created | A new deal is added |
| deal.stage_changed | A deal moves to a different pipeline stage |
| deal.won | A deal is marked won |
| deal.lost | A deal is marked lost |
| invoice.paid | An invoice transitions to paid |
| requirement.met | A deal's document requirement is fulfilled — a document attached, or waived |
| contact.created | A new contact is added, including via CSV import, the API, or the AI assistant |
These seven are the whole public vocabulary today. Reopening a deal (won or lost back to open) does not emit anything, and neither does un-paying an invoice or clearing a fulfillment back to unmet.
Subscribe to a webhook
- 1Open Settings → Integrations. Only the workspace owner can create, delete, or re-enable a webhook.
- 2Under Webhooks, set up a subscription: an https:// URL, one or more events, and a format — plain JSON, or a Slack-compatible message for an incoming-webhook channel URL.
- 3Copy the signing secret. It starts with nk_whsec_ and is shown only once.
URLs must be https — the payload carries deal data and is worth protecting in transit. Up to 10 subscriptions per workspace. Loopback, private, link-local, and other internal hosts are also rejected, both when you save the URL and again right before every delivery.
Delivery semantics
- Every delivery is signed with HMAC-SHA256 over the raw body — see verify webhook signatures for the exact scheme and a verifier.
- Each attempt times out after 10 seconds. Any 2xx response counts as success; anything else, a timeout, or a redirect counts as a failure (redirects are never followed).
- A failed delivery retries with backoff: roughly 30s, 1m, 2m, 4m, 8m, 16m, 32m, then 64m — 8 retries, about 2 hours of attempts, before that delivery is exhausted.
- Delivery is at-least-once, not exactly-once: if a batch of deliveries is interrupted partway through, the retry re-sends to subscriptions that already got a copy.
- Deliveries are not ordered. Sort by occurredAt in the payload, not by arrival time.
X-Neokivo-Delivery identifies a delivery attempt, not the underlying event — a duplicate delivery of the same event carries a different id. If you need idempotency, key on the payload's content instead: (event, data.deal.id, occurredAt).
Disabled webhooks
After 20 consecutive deliveries exhaust every retry with no success in between — roughly 180 failed requests over several days to a truly dead endpoint — the subscription disables itself and you get a webhook.disabled notification, addressed to the webhook's creator if they're still a member, or to every workspace owner if not.
The same thing happens immediately, independent of that counter, if the subscription's creator leaves the workspace: deliveries stop rather than falling back to workspace-wide visibility. This is the same fail-closed rule API keys and the MCP server follow.
Re-enabling a disabled webhook is owner-only, from Settings. It resets the failure count, and makes the re-enabling owner the subscription's new creator — deliveries from then on are scoped to what they can see.
The payload
Every delivery is a versioned envelope: the event name, when it happened, the workspace, and the record the event is about — a deal, invoice, requirement, or contact, named by the event family — plus the from/to stage names on a stage change. See verify webhook signatures for the full envelope shape, a real sample, and how to check the signature before you trust the body.
Visibility
- deal.*, invoice.paid, and requirement.met are gated by deal visibility: a delivery only goes out if the deal (the one that changed, or the one the invoice/requirement belongs to) is visible to whoever created the subscription. An invoice with no linked deal has nothing to gate on, so it delivers to every matching subscription.
- contact.created has no deal to gate on — contacts are visible workspace-wide, so it delivers to every matching subscription regardless of who created it.
Spotted something out of date? Email hello@neokivo.com.