Webhooks
Receive real-time HTTP callbacks whenever something interesting happens — opens, clicks, replies, meetings booked, deals won. All deliveries are signed with HMAC-SHA256.
Register an endpoint
Add a webhook URL in Settings → Webhooks, select the events you care about, and copy the signing secret. You can also create webhooks programmatically via the API (see /api/v1/outbound-webhooks).
Delivery & retry policy
- Method:
POSTwithContent-Type: application/json. - Timeout: 10 seconds. Respond with any 2xx status to acknowledge.
- Retries: Up to 3 attempts on failure, with backoff
[10s, 60s, 300s]. After the final failure the event is marked dead and visible in Settings → Webhooks. - Ordering: Not guaranteed. Use
occurred_atto order events on your side. - De-dup: Every delivery carries an
X-WarmDeals-Delivery-Idheader. Store it and reject duplicates.
Signature verification
Each request includes a header X-WarmDeals-Signature containing the HMAC-SHA256 of the raw request body using your webhook secret (hex-encoded).
Python
import hmac, hashlib
def verify(secret: str, body: bytes, signature: str) -> bool:
expected = hmac.new(
secret.encode(),
body,
hashlib.sha256,
).hexdigest()
return hmac.compare_digest(expected, signature)Node.js / TypeScript
import { createHmac, timingSafeEqual } from "node:crypto"
export function verify(secret: string, body: Buffer, signature: string): boolean {
const expected = createHmac("sha256", secret).update(body).digest("hex")
const a = Buffer.from(expected, "hex")
const b = Buffer.from(signature, "hex")
return a.length === b.length && timingSafeEqual(a, b)
}Important: verify against the raw, unparsed request body. JSON parsing can change byte representation (key order, whitespace) and break the signature.
Event types
lead.foundA new lead/company was discovered (manually, via parsing, or via import).
{
"event": "lead.found",
"occurred_at": "2026-05-22T10:14:33Z",
"data": {
"id": "01HXY...",
"name": "Acme Corp",
"email": "ceo@acme.com",
"source": "csv_import"
}
}email.openedRecipient opened an outbound email (tracking pixel hit).
{
"event": "email.opened",
"occurred_at": "2026-05-22T10:17:01Z",
"data": {
"campaign_id": "01HXY...",
"company_id": "01HXY...",
"message_id": "01HXY...",
"user_agent": "Mozilla/5.0 ..."
}
}email.clickedRecipient clicked a tracked link inside an outbound email.
{
"event": "email.clicked",
"occurred_at": "2026-05-22T10:17:42Z",
"data": {
"campaign_id": "01HXY...",
"company_id": "01HXY...",
"url": "https://yourdomain.com/pricing"
}
}email.bouncedOutbound email hard-bounced. Lead is auto-suppressed.
{
"event": "email.bounced",
"occurred_at": "2026-05-22T10:18:00Z",
"data": {
"campaign_id": "01HXY...",
"company_id": "01HXY...",
"reason": "550 5.1.1 user unknown"
}
}campaign.startedA campaign has begun sending.
{
"event": "campaign.started",
"occurred_at": "2026-05-22T10:00:00Z",
"data": { "campaign_id": "01HXY...", "total_recipients": 1240 }
}campaign.finishedA campaign finished sending all messages.
{
"event": "campaign.finished",
"occurred_at": "2026-05-22T11:30:00Z",
"data": {
"campaign_id": "01HXY...",
"sent": 1240,
"delivered": 1198,
"bounced": 42
}
}reply.receivedAn inbound reply landed in the unified inbox.
{
"event": "reply.received",
"occurred_at": "2026-05-22T10:30:12Z",
"data": {
"thread_id": "01HXY...",
"from": "ceo@acme.com",
"subject": "Re: Quick question",
"snippet": "Sure — happy to chat tomorrow at 2pm.",
"sentiment": "positive"
}
}meeting.bookedA meeting was scheduled via the public booking link.
{
"event": "meeting.booked",
"occurred_at": "2026-05-22T10:45:00Z",
"data": {
"meeting_id": "01HXY...",
"company_id": "01HXY...",
"start_at": "2026-05-25T14:00:00Z",
"duration_minutes": 30
}
}deal.wonA deal advanced to a won stage.
{
"event": "deal.won",
"occurred_at": "2026-05-22T16:00:00Z",
"data": {
"deal_id": "01HXY...",
"company_id": "01HXY...",
"value": 12000,
"currency": "USD"
}
}sequence.step_sentA sequence step was sent to a recipient.
{
"event": "sequence.step_sent",
"occurred_at": "2026-05-22T09:00:00Z",
"data": {
"sequence_id": "01HXY...",
"step_index": 2,
"company_id": "01HXY...",
"channel": "email"
}
}voice.completedAn outbound AI voice call finished. Transcript & disposition included.
{
"event": "voice.completed",
"occurred_at": "2026-05-22T11:12:00Z",
"data": {
"campaign_id": "01HXY...",
"company_id": "01HXY...",
"duration_seconds": 87,
"disposition": "interested",
"transcript_url": "https://warm.deals/transcripts/01HXY..."
}
}sms.deliveredSMS was successfully delivered (per carrier receipt).
{
"event": "sms.delivered",
"occurred_at": "2026-05-22T12:00:00Z",
"data": {
"campaign_id": "01HXY...",
"company_id": "01HXY...",
"provider_message_id": "SM..."
}
}Manage your webhooks
Add, pause, rotate secrets, and replay failed deliveries from Settings → Webhooks.