DOCS

Webhooks

The short answer

Register an endpoint, pick the events you want, and SealDeal POSTs a signed JSON envelope to your URL each time one fires. Verify the X-Outbound-Signature header before trusting a payload. There are 54 events you can subscribe to today, and a failed delivery is retried 6 times over roughly 24 hours before it is marked dead.

The envelope

Every delivery has the same outer shape. Event-specific fields live under `data`, so you can route on `type` before parsing anything else.

POST https://your-endpoint.example.com/hooks
Content-Type: application/json
X-Outbound-Signature: t=1753027200,v1=9f86d081...

{
  "apiVersion": "2026-07-21",
  "id": "evt_01H...",
  "type": "reply.received",
  "createdAt": "2026-07-30T12:00:00.000Z",
  "organizationId": "org_01H...",
  "data": { }
}

Verifying a signature

The header carries a timestamp and a hex HMAC-SHA256. The signed string is the timestamp, a literal dot, then the raw request body. Sign the raw bytes you received: re-serializing the parsed JSON will not reproduce the signature, which is the single most common reason a first integration fails.

import crypto from 'node:crypto'

function verify(rawBody, header, secret) {
  const parts = Object.fromEntries(
    header.split(',').map((kv) => kv.split('='))
  )
  const expected = crypto
    .createHmac('sha256', secret)
    .update(`${parts.t}.${rawBody}`)
    .digest('hex')

  // Constant-time compare, never ===
  const a = Buffer.from(expected, 'hex')
  const b = Buffer.from(parts.v1, 'hex')
  return a.length === b.length && crypto.timingSafeEqual(a, b)
}

The signing secret is shown once when the endpoint is created and is stored only as a hash, so it cannot be recovered later. Rotate it with POST /api/v1/webhooks/{id}/rotate-secret if it leaks. Format: whs_ followed by 32 hex characters.

Delivery, retries and failure

Each attempt times out after 10 seconds. Return any 2xx to acknowledge. A 5xx or a timeout is retried; a 4xx is treated as a permanent rejection and is not. There are 7 attempts in total, the first plus 6 retries:

  • Attempt 2, 1 minute after the previous one.
  • Attempt 3, 5 minutes after the previous one.
  • Attempt 4, 15 minutes after the previous one.
  • Attempt 5, 1 hour after the previous one.
  • Attempt 6, 6 hours after the previous one.
  • Attempt 7, 24 hours after the previous one.

Deliveries are inspectable over the API: list them with GET /api/v1/webhooks/{id}/deliveries, read one in full, and replay a failed one once your endpoint is healthy again. Endpoints that keep failing are disabled rather than retried forever.

Design your handler to be idempotent on the envelope `id`. Retries mean the same event can arrive more than once, and a handler that charges, emails or creates on every receipt will do so twice.

Events you can subscribe to

54 events fire today. 5 of them are `rule.*` events your own custom rules emit, so you can trigger on conditions you define rather than only on platform lifecycle.

action.*

Action items appearing on and leaving the rep work queue.

action.resolved

company.*

Account records and their research.

company.research_completed

contact.*

Contact lifecycle and research.

contact.deleted · contact.erased · contact.snoozed · contact.stage_changed · contact.tagged

deal.*

Pipeline movement, including stage transitions and terminal outcomes.

deal.created · deal.lost · deal.stage_changed · deal.won

demo.*

demo.abandoned · demo.run · demo.signup_clicked

draft.*

The outbound draft lifecycle, from generation through approval to send.

draft.approved · draft.created · draft.failed · draft.pending_approval · draft.sent

email.*

Delivery telemetry from the mail provider: opens, clicks, bounces, complaints.

email.bounced · email.clicked · email.complained · email.opened · email.unsubscribed

grounding.*

grounding.would_fire

handoff.*

handoff.created

inbound.*

inbound.lead_received

lead.*

Inbound leads captured, qualified and routed.

lead.interested

list.*

Contact lists and their contents.

list.context_updated · list.uploaded

meeting.*

Meetings booked against a deal.

meeting.booked

org.*

Organization-level configuration changes.

org.health_changed · org.research_ttl_updated

quota.*

quota.warning

rep.*

Team membership changes.

rep.activated · rep.invited

reply.*

Inbound replies and their classified intent.

reply.classified · reply.received

research.*

research.completed

rule.*

Events a custom rule emits through its call_webhook action, so you can trigger on your own conditions rather than only on platform lifecycle.

rule.deal_at_risk · rule.hot_reply · rule.lead_qualified · rule.objection · rule.triggered

send.*

send.failed

sequence.*

Multi-step outreach state.

sequence.completed · sequence.paused · sequence.resumed · sequence.started · sequence.step_failed

suppression.*

Addresses and domains entering the do-not-contact list.

suppression.added · suppression.removed

team.*

team.member_added · team.member_removed

Reserved event names that do not fire yet

These 14 names are catalogued and accepted by the subscription API, but nothing emits them today. Subscribing to one is valid and will simply never deliver, so treat this list as a roadmap rather than a feature. They are listed here because a silent integration is worse than a missing one.

action.created · company.created · deal.risk_changed · draft.regenerated · list.contacts_added · meeting.cancelled · meeting.completed · meeting.scheduled · org.created · org.seller_profile_updated · payment.failed · quota.exceeded · subscription.canceled · subscription.changed

See also: Docs home · API reference · API errors · Integrations