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.
Deliveries are batched. A single request contains one or more dial events
for the same company (up to 100 per request). When traffic is light you may
get batches of one; under load expect tens. Always loop over the dials
array.
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.batch. |
X-OutboundIQ-Delivery-Id | A unique UUID per HTTP 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 DialBatchWebhookPayload = { event: "dial.batch";
// UUID — also sent as the X-OutboundIQ-Delivery-Id header deliveryId: string;
// ISO 8601 UTC timestamp of when we sent this batch deliveredAt: string;
// 1..100 dial events, all for the same company dials: DialEvent[];};
type DialEvent = { // 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;
// ISO 8601 UTC, e.g. "2026-04-10T18:32:15.000Z" 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;};Example payload
Section titled “Example payload”{ "event": "dial.batch", "deliveryId": "550e8400-e29b-41d4-a716-446655440000", "deliveredAt": "2026-04-10T18:32:16.842Z", "dials": [ { "companySlug": "abcde123-4567-8901-2345-67890abcdef0", "callId": "AB12345", "callDirection": "outbound", "timestamp": "2026-04-10T18:32:15.000Z", "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 } ]}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. Failures are at the batch level: if a delivery fails after retry, every dial in that batch is dropped.
Each webhook is delivered independently. If you have multiple webhooks for the same company 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.