Webhooks

Back to the guide

A webhook is a message Deeluge sends to your server the moment something happens — a record is created, a deal moves stage, someone fills in a form — so you do not have to keep asking the API whether anything changed.

Two ways to set one up

  • In the app: an owner or admin adds one under Settings → Webhooks → Outgoing. See Webhooks in Settings.
  • Through the API: an integration subscribes itself with its API key. The key needs webhooks:write, and the read scope of every event it asks for.
POST /api/v1/webhooks
Authorization: Bearer sf_live_…
Content-Type: application/json

{ "name": "Sync to warehouse", "url": "https://example.com/deeluge",
  "events": ["record.created", "record.updated"], "objects": ["accounts"] }

The answer carries the webhook's signing secret (whsec_…) once. A key sees and changes only the webhooks it made.

Events

GET /api/v1/event-types lists them all with the scope each needs.

Event When
record.created A record is created — in the app, by import, through the API, by a form or an incoming webhook
record.updated A record's values change; changed_attributes says which
record.deleted A record is deleted
list_entry.created, list_entry.deleted A record is added to, or taken off, a list
note.created A note, call or meeting is logged
task.created, task.updated A task is created, or changes
form.submitted Someone submits one of the firm's forms

Subscribe to a family with record.*, or to everything with *.

What arrives

A POST with a JSON body:

{
  "id": "evt_6c1a…",
  "type": "record.updated",
  "version": 1,
  "created_at": "2026-10-26T15:04:05.000Z",
  "workspace_id": "…",
  "actor": { "type": "user", "id": "…" },
  "data": {
    "record": { "id": "…", "object": "opportunities", "title": "Acme buyout", "values": { "stage": "…" } },
    "changed_attributes": ["stage"]
  }
}

data.record has exactly the shape GET /api/v1/records/{id} returns, as it was when the message was built. A webhook carries only what its identity may see: restricted records are left out until someone grants that webhook (or that key) access.

Checking it came from us

Every message carries three headers, following the Standard Webhooks specification:

Header
webhook-id The message id. The same on every retry — use it to ignore duplicates.
webhook-timestamp Unix seconds. Refuse anything more than five minutes old.
webhook-signature v1, then the base64 HMAC-SHA256 of id.timestamp.body, keyed with the secret's bytes after whsec_.

Any Standard Webhooks library does all of this:

import { Webhook } from 'standardwebhooks';
const wh = new Webhook(process.env.DEELUGE_WEBHOOK_SECRET);
const event = wh.verify(rawBody, headers); // throws if anything is wrong

Verify against the raw body, before any JSON parsing.

Answering

Answer with any 2xx within ten seconds. Do slow work afterwards, from a queue.

Anything else is retried after 1 minute, 5 minutes, 30 minutes, 2 hours, 6 hours, 12 hours and 24 hours — eight attempts over about two days. Answering 410 Gone turns the webhook off straight away. A webhook that has failed fifty times in a row and not succeeded for three days is turned off too; turn it back on from Settings or with PATCH /api/v1/webhooks/{id} and {"status": "active"}.

Delivery is at least once, and messages can arrive out of order. Use webhook-id to de-duplicate and created_at to order.

Changes you made yourself

A key's webhooks are not sent events caused by requests made with that same key. An integration that writes back to the CRM therefore does not trigger itself in a loop. actor says who made every other change.

Inspecting and replaying

  • GET /api/v1/webhooks/{id}/deliveries?status=failed — the last deliveries and what the receiver answered.
  • POST /api/v1/webhooks/{id}/deliveries/{deliveryId}/retry — send one again, with the same body and webhook-id.
  • POST /api/v1/webhooks/{id}/test — send a webhook.test event now.
  • POST /api/v1/webhooks/{id}/secret — roll the signing secret.

Deliveries are kept for 30 days.

Receiving: incoming webhooks

The other direction — another tool creating records in Deeluge by posting to a URL — is set up in Settings → Webhooks → Incoming. See Webhooks in Settings.

Next

Webhooks · Deeluge docs