Webhooks
outboundIQ can POST a JSON payload to one or more URLs of your choice every time we finish cleaning a dial. Use webhooks to forward dial events to your CRM, your data warehouse, or any internal pipeline.
Configuring webhooks
Section titled “Configuring webhooks”Webhooks are managed per company under Settings → Webhooks in the workspace UI.
You can configure up to 3 webhooks per company. Each webhook has:
- A short name (for your own bookkeeping).
- A destination URL (HTTPS only).
- Optional custom headers (for example an
X-API-Keyyour receiver expects). - An enabled / disabled toggle. Disabled webhooks receive nothing.
The first time you create a webhook we generate a signing secret and display it once. Save it somewhere safe — you will not be able to view it again. If you lose it, use Rotate signing secret to generate a new one (the old one will stop working immediately).
Method
Section titled “Method”Every delivery is an HTTP POST with a Content-Type: application/json body.
Headers we send
Section titled “Headers we send”In addition to any custom headers you configured, every request includes:
| Header | Value |
|---|---|
Content-Type | application/json |
User-Agent | outboundIQ-webhooks/1.0 |
X-OutboundIQ-Event | The event type. Currently always dial. |
X-OutboundIQ-Delivery-Id | A unique UUID per delivery. Use it for idempotency on your side. |
X-OutboundIQ-Signature | sha256=<hex> — HMAC-SHA256 of the raw body using your signing secret. |
Payload schema
Section titled “Payload schema”type DialWebhookPayload = { event: "dial";
// UUID — also sent as the X-OutboundIQ-Delivery-Id header deliveryId: string;
// Your company slug companySlug: string;
// Dial / call ID. For inbound calls this is prefixed with "IB_". callId: string | null;
// "outbound" | "inbound" | "manual" | "sms" | "transfer" | ... callDirection: string;
// EST/EDT, format: "YYYY-MM-DD HH:MM:SS" timestamp: string;
// Outbound caller ID (the number we dialed FROM), 10-digit US. ani: string | null;
// Prospect / customer phone (the number we dialed TO), 10-digit US. phone: string | null;
// Human-readable campaign name, post-translation. campaign: string | null;
// Internal campaign identifier. campaignInternalId: string | null;
// The agent who handled the call. agent: string | null;
// Disposition / call result, post-translation. disposition: string | null;
// Original disposition identifier from the dialer. dispositionId: string | null;
// Was contact made with the prospect? contact: boolean;
// Did the call result in a successful outcome? success: boolean;
// Was the disposition a system disposition (auto-set by the dialer)? isSystemDispo: boolean;
// Total dial attempts on this lead, when known. totalDialAttempts: number | null;
// "five9" | "five9ca" | "calltools" | "ringcx" | "newbridge" | ... dialerFamily: string;};Example payload
Section titled “Example payload”{ "event": "dial", "deliveryId": "550e8400-e29b-41d4-a716-446655440000", "companySlug": "abcde123-4567-8901-2345-67890abcdef0", "callId": "AB12345", "callDirection": "outbound", "timestamp": "2026-04-10 14:32:15", "ani": "5551234567", "phone": "5559876543", "campaign": "Q2 Outbound Push", "campaignInternalId": "abc-123", "agent": "Jane Doe", "disposition": "Sale", "dispositionId": "42", "contact": true, "success": true, "isSystemDispo": false, "totalDialAttempts": 3, "dialerFamily": "five9"}Verifying the signature
Section titled “Verifying the signature”Always verify the X-OutboundIQ-Signature header before trusting the payload.
Compute HMAC-SHA256 of the raw request body using your signing secret and
compare it to the sha256=<hex> value.
Node.js
Section titled “Node.js”import { createHmac, timingSafeEqual } from "node:crypto";
function verifySignature(rawBody, header, secret) { if (!header || !header.startsWith("sha256=")) return false; const expected = createHmac("sha256", secret).update(rawBody).digest("hex"); const provided = header.slice("sha256=".length); const a = Buffer.from(expected, "hex"); const b = Buffer.from(provided, "hex"); if (a.length !== b.length) return false; return timingSafeEqual(a, b);}Python
Section titled “Python”import hmac, hashlib
def verify_signature(raw_body: bytes, header: str, secret: str) -> bool: if not header or not header.startswith("sha256="): return False expected = hmac.new(secret.encode(), raw_body, hashlib.sha256).hexdigest() return hmac.compare_digest(expected, header[len("sha256="):])Retries
Section titled “Retries”If your endpoint returns a non-2xx status or fails to respond within 10 seconds, we retry the delivery once after about 500ms. If the retry also fails we log the failure and give up — we do not redeliver later.
Each webhook is delivered independently. If you have multiple webhooks for the same dial and one fails, the others still receive their delivery.
To handle missed deliveries, treat the X-OutboundIQ-Delivery-Id as an
idempotency key on your side and reconcile periodically against the Dials
API.
Disabling and deleting webhooks
Section titled “Disabling and deleting webhooks”Disabling a webhook stops deliveries immediately. Deleting it removes the webhook entirely and rotates out the signing secret. If all your webhooks are disabled or deleted, your company drops out of the webhook pipeline entirely until you enable or create one again.