Overview
Configure a destination URL from Settings → Webhooks. Pick the events you care about; Cold OS POSTs each event as a single JSON body to your endpoint. Delivery is at-least-once, retried with exponential backoff on any non-2xx response.
Event catalog
contact.createdA new contact was inserted.contact.updatedA field on a contact changed.contact.stage_changedThe contact's `internal_stage` was set.email.sentAn outbound email left the building (Gmail accepted).email.openedFirst open per (contact, day).email.clickedFirst click per (contact, day, link).email.repliedAn inbound reply was attributed to a sent email.email.bouncedHard bounce detected via Gmail event stream.email.unsubscribedRFC 8058 unsubscribe header or in-body opt-out.call.completedA call's status webhook arrived from Twilio.meeting.scheduledA Cold OS calendar event was created.meeting.completedA scheduled meeting elapsed.task.createdA task was created (manual or flow-spawned).task.completedA task was marked complete.flow.enrolledA contact was enrolled in a flow.flow.exitedAn enrollment terminated (any reason). Detail in payload.flow.goal_reachedAn enrollment reached a Goal node.
Delivery
Headers on every delivery:
Content-Type: application/jsonX-ColdOS-Event— the event name (e.g.contact.created).X-ColdOS-Delivery— unique delivery id (use for idempotency).X-ColdOS-Timestamp— Unix seconds when we signed the payload.X-ColdOS-Signature—v1=<hex>HMAC over{timestamp}.{body}.
{
"id": "evt_01HZSDD4E5F6G7H8J9K0L1M2N3",
"event": "contact.stage_changed",
"occurred_at": "2026-05-01T14:30:00.000+05:30",
"data": {
"contact_id": "c2a8a93a-2f1c-4c5b-8f3d-9c1e3a8b5d12",
"from_stage": "engaged",
"to_stage": "meeting_booked",
"actor_user_id": "b1f2c3d4-5678-90ab-cdef-1234567890ab"
}
}Verifying signatures
Algorithm
Compute HMAC_SHA256(signing_secret, "{X-ColdOS-Timestamp}.{raw_body}") and compare hex-encoded with the v1= portion of X-ColdOS-Signature. Use a constant-time comparison.
import crypto from "node:crypto";
export function verify(req, signingSecret) {
const ts = req.headers["x-coldos-timestamp"];
const sig = req.headers["x-coldos-signature"];
if (!ts || !sig) return false;
const ageSec = Math.abs(Date.now() / 1000 - Number(ts));
if (ageSec > 300) return false;
const expected = crypto
.createHmac("sha256", signingSecret)
.update(`${ts}.${req.rawBody}`)
.digest("hex");
const presented = sig.replace(/^v1=/, "");
return crypto.timingSafeEqual(
Buffer.from(expected, "hex"),
Buffer.from(presented, "hex"),
);
}Retries
Cold OS retries any non-2xx response on the schedule below. After the final attempt the delivery row stays in the audit log — retry it manually from the dashboard at any time.
- 1m → 5m → 30m → 2h → 8h → 1d (six attempts total)