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.
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.
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.
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.
Verify the signature in constant time, return 2xx inside ten seconds, process async. That is the whole contract.
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 } } 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.
Every inbound that lands on a sequenced thread, with the classifier verdict attached.
reply.receivedreply.classified Hard signals from mailbox providers and recipient action. Suppressed workspace-wide on first pass.
bounce.hardbounce.softcomplaint.receivedunsubscribe.received Lifecycle of each sender, from the first warmup pass through quarantine and back.
mailbox.connectedmailbox.quarantinedmailbox.recovered Pipeline state the dashboard shows you, in the same shape the dashboard reads.
sequence.startedsequence.completedcampaign.paused Contacts, deals, pipeline stage changes mirrored to the integration on the same event.
contact.createddeal.stage_changed events: ["*"] Or pick by surface, by type, or by a single sequence or mailbox. The dispatcher filters before the network hop.
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.
Delivery closes. A duplicate retry from us is a no-op on your side.
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 |
The whole rate policy, in one row.
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.