API and webhooks The same surface our dashboard runs on

REST in.
Signed events out.

Scoped keys with per-minute limits. Idempotent writes. HMAC-signed webhooks with five retries and a dead-letter view. No hidden surface, no second-class API.

Section 1 · Quickstart

Key to first event in three steps.

No OAuth dance, no app review queue. Create a key, point us at an endpoint, and the first signed event shows up the moment something happens in your workspace.

Create a scoped key Step 1
wmbly_live_8f3kq2… 600 req/min
events:read mailboxes:write

Pick scopes in the dashboard. The key can never do more than what you picked, and a cross-scope call returns 403 with the scope it was missing.

Register your endpoint Step 2
POST api.acme.dev/hooks/warmbly
whsec_9d41kk… HMAC-SHA256

Paste a URL, get a signing secret. Subscribe to one surface or the whole stream, and rotate the secret any time without losing delivery history.

Catch the first event Step 3
type    reply.classified
verdict positive
your reply 200 OK · 41ms

Verify the signature in constant time, return 2xx inside ten seconds, process async. That is the whole contract.

Keys and endpoints live in Settings · every delivery is inspectable After step three it is just HTTP
Section 2 · Pick your client

One operation. Every client.

A published OpenAPI 3.1 spec you can generate a typed client from, and plain HTTP from anywhere else. Same request shape, same idempotency semantics, same error codes.

// list-campaigns.ts — plain fetch, no SDK required
const res = await fetch(
  "https://api.warmbly.com/v1/campaigns?limit=20",
  { headers: { Authorization: `Bearer ${process.env.WARMBLY_KEY}` } },
);

const { data, pagination } = await res.json();
console.log(data.length, pagination.next_cursor);
# list_campaigns.py — plain requests, no SDK required
import os, requests

res = requests.get(
    "https://api.warmbly.com/v1/campaigns",
    headers={"Authorization": f"Bearer {os.environ['WARMBLY_KEY']}"},
    params={"limit": 20},
)

body = res.json()
print(len(body["data"]), body["pagination"]["next_cursor"])
// main.go — net/http, no SDK required
req, _ := http.NewRequest("GET",
    "https://api.warmbly.com/v1/campaigns?limit=20", nil)
req.Header.Set("Authorization", "Bearer "+os.Getenv("WARMBLY_KEY"))

res, err := http.DefaultClient.Do(req)
if err != nil { log.Fatal(err) }
defer res.Body.Close() // decode { data, pagination }
# terminal
$ curl https://api.warmbly.com/v1/campaigns?limit=20 \
    -H "Authorization: Bearer $WARMBLY_KEY"

# -> { "data": [ ... ], "pagination": { "next_cursor": "c1_…", "has_more": true } }
Section 3 · Event catalog

Five surfaces, one firehose.

Subscribe to the whole stream or pick the surfaces you care about. The same event the dashboard reads is the event you receive, in the same shape.

Replies 2 events

Every inbound that lands on a sequenced thread, with the classifier verdict attached.

reply.receivedreply.classified
Deliverability 4 events

Hard signals from mailbox providers and recipient action. Suppressed workspace-wide on first pass.

bounce.hardbounce.softcomplaint.receivedunsubscribe.received
Mailboxes 3 events

Lifecycle of each sender, from the first warmup pass through quarantine and back.

mailbox.connectedmailbox.quarantinedmailbox.recovered
Sequences 3 events

Pipeline state the dashboard shows you, in the same shape the dashboard reads.

sequence.startedsequence.completedcampaign.paused
CRM 2 events

Contacts, deals, pipeline stage changes mirrored to the integration on the same event.

contact.createddeal.stage_changed
Subscribe to all
events: ["*"]

Or pick by surface, by type, or by a single sequence or mailbox. The dispatcher filters before the network hop.

Section 4 · Delivery

Signed bodies. Replayable. Dead-letter visible.

Every event is HMAC-signed with your endpoint secret and carries an idempotency key. A 2xx in ten seconds closes the delivery. Anything else, and we walk a five-step retry schedule before the event lands in a view you can replay from.

  • Signed with your secret. Verify the signature in constant time before you trust the body.
  • Ten seconds to ack. Return 2xx fast and process async.
  • Dead-letter is a screen. Inspect, replay a range, rotate the secret without losing history.
POST /webhooks/yours
reply.classified
at-least-once
Event fires on the consumer Start
Payload signed t+ts.body
id   evt_01HQX9F7P3A8KY2NJM4R6BWT0S
type reply.classified
idem reply:th_01HQX…
2xx in under 10s acknowledged

Delivery closes. A duplicate retry from us is a no-op on your side.

non-2xx or timeout walk the retry schedule
01 +30s Most transient outages resolve here.
02 +2m Receivers that throttle briefly recover.
03 +10m Covers a short rolling restart.
04 +1h Covers a maintenance window.
05 +6h Last attempt before dead-letter.
After 5 failed attempts dead-letter view
Signed payload, idempotent on event id Replay any range from the dashboard
Section 5 · Per-key scopes

Scope down. Stay down.

Six resources, two verbs, one admin scope. A cross-scope call returns 403 with scope_required and the exact scope the call needs.

  • read List, inspect, subscribe.
  • write Create, mutate, delete.
  • admin Workspace, billing, members.
Resource Read Write Admin
Mailboxes mailboxes:read mailboxes:write
Sequences sequences:read sequences:write
Contacts contacts:read contacts:write
Events events:read
CRM crm:read crm:write
Workspace admin:write
403 scope_required carries the exact scope the call needs Keys can also be pinned to one mailbox or sequence
Section 6 · By the numbers

The whole rate policy, in one row.

600 / min
default per-key limit
per key, not per workspace
120 / min
read-only key limit
reads get their own lane
5 tries
webhook retry attempts
backoff from 30s to 6h
10 s
webhook delivery timeout
then it counts as a miss
24 h
idempotency-key window
same key, same result
256 KB
event payload ceiling
full body, never truncated
Developer FAQ

The questions we get a lot.

Full API reference and the OpenAPI spec at docs.warmbly.com.

Compute HMAC-SHA256 of the raw request body using your endpoint secret. Compare the hex digest to the X-Warmbly-Signature header in constant time. Reject any request older than 5 minutes against X-Warmbly-Timestamp.

No. We deliver at least once. Every event carries an idempotency_key. Persist it on first receipt and skip duplicates. Retries happen on any non-2xx response or a timeout above 10 seconds.

The event lands in the dead-letter view in the dashboard. From there you can inspect the last failure, replay one event or a range, or rotate the endpoint secret without resetting delivery history.

Yes. Each key carries a list of scopes and an optional resource filter, for example mailboxes:write limited to mbx_01HQX. Cross-resource calls return 403 with a scope_required error code.

Yes, at docs.warmbly.com/openapi.json. It is OpenAPI 3.1, so you can import it into Postman or Insomnia, generate a typed client in your language, or build straight against it. There are no official SDKs yet, so plain HTTP is the path today.

Wire Warmbly into your stack.

Create a key, paste a webhook URL, ship the integration before lunch.