Browse documentation

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

EventFires when
deal.createdA new deal is added
deal.stage_changedA deal moves to a different pipeline stage
deal.wonA deal is marked won
deal.lostA deal is marked lost
invoice.paidAn invoice transitions to paid
requirement.metA deal's document requirement is fulfilled — a document attached, or waived
contact.createdA 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

  1. 1Open Settings → Integrations. Only the workspace owner can create, delete, or re-enable a webhook.
  2. 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.
  3. 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.