API and Webhooks

Programmatic access to Defrost via the public API and outbound webhooks for event integrations.

Public API v1

Available on Growth+ plans. Settings → API & webhooks shows your API key (created on demand; never displayed twice).

Base URL: https://api.defrost.email/v1

Authentication: Bearer token in the Authorization header.

Common endpoints

Campaigns

  • GET /v1/campaigns — list campaigns (paginated; ?status= filter; ?page= + ?page_size=)
  • POST /v1/campaigns — create a campaign with goal + ICP overrides + sequence (use ?autolaunch=true to immediately kick the pipeline)
  • GET /v1/campaigns/:id — fetch campaign state including step status, ICP snapshot, and metrics

Contacts

  • GET /v1/contacts — list contacts (paginated; ?page= + ?page_size=)
  • POST /v1/contacts — bulk upsert contacts (max 500 per request; requires GDPR lawful_basis; pre-filters against org-level suppressions)

Replies

  • GET /v1/replies — list replies (paginated; ?classification= filter; body text transparently decrypted)

Webhooks

  • GET /v1/webhooks — list registered outbound webhooks
  • POST /v1/webhooks — register an outbound webhook (target_url, events[], optional description)
  • DELETE /v1/webhooks/:id — unregister an outbound webhook

Limited beta (contact support to enable)

  • POST /v1/leads/discover — kick off ad-hoc lead discovery for a campaign (currently returns 501 Not Implemented; full implementation in v2.4)
  • POST /v1/campaigns/:id/send — manually trigger send for a specific campaign (currently returns 501 Not Implemented; full implementation in v2.4)

Planned for v2.4 — NOT yet shipped: POST /v1/campaigns/:id/launch (use ?autolaunch=true on POST-create today), GET /v1/campaigns/:id/leads, POST /v1/leads/:id/replies, GET /v1/workspaces, POST /v1/workspaces/:id/icp.

Full reference (interactive Swagger UI): /docs/api

Rate limits

  • Limits are aggregated per organization across all API keys belonging to the same org. Minting additional API keys does NOT multiply your plan's rate limit.
  • Plan-tier ceilings (requests per minute, per org):
    • Free: 60/min
    • Starter: 1,000/min
    • Growth: 5,000/min
    • Scale: 10,000/min
    • Enterprise: 30,000/min

Exceeded limits return 429 Too Many Requests with a Retry-After header.

Errors

JSON errors with shape { "error": { "code": "...", "message": "...", "details": {...} } }. Codes:

  • unauthorized — bad or missing API key
  • forbidden — key valid but no access to the resource (e.g., wrong workspace, missing scope)
  • not_found — resource doesn't exist
  • validation_failed — invalid request body; details lists which fields
  • rate_limit — see Retry-After header
  • not_implemented — endpoint accepted but business logic deferred to v2.4
  • internal — Defrost-side issue; safe to retry with backoff

Webhooks

Outbound webhooks fire on key events. Settings → API & webhooks → Add endpoint to register a URL.

Supported events

The following events are supported today and emit live deliveries:

  • lead.verified — email verification completed (whether passed or failed)
  • send.bounced — recipient mailbox rejected an outgoing send
  • reply.received — inbound reply (raw, before classification)
  • reply.classified — Haiku classifier finished labeling the reply
  • campaign.completed — all sequence touches finished or campaign was paused

Coming soon (enum registered; dispatcher pending — do not subscribe yet):

  • meeting.booked — booking detected (no live fire-site today)

Planned for v2.4 — NOT yet shipped: lead.discovered, email.sent, email.delivered, email.opened, email.clicked. These events do not currently emit deliveries.

Payload shape

Each event POSTs JSON: { "event": "lead.verified", "occurred_at": "...", "organization_id": "...", "data": { ... } }.

Signing

Webhooks are signed with HMAC-SHA256 over the request body. The signature is in the X-Defrost-Signature header (format: sha256=<hex>). Verify before trusting:

const expected = hmacSha256(yourSigningSecret, requestBody)
if (!constantTimeCompare(`sha256=${expected}`, headers['x-defrost-signature'])) reject()

Additional headers:

  • x-defrost-event — event type
  • x-defrost-delivery-id — UUID that is stable across retry attempts for idempotent dedup
  • x-defrost-timestamp — unix seconds (reject events older than ~5 min to defend against replay)

Retry policy

Defrost retries failed webhooks (non-2xx response or timeout) with exponential backoff: 1m, 5m, 30m, 2h, 12h. After 5 failed retries the endpoint is auto-disabled and you receive an email alert.

Use x-defrost-delivery-id to deduplicate — the header value is the same UUID across all retries of the same event.

What to read next