Webhooks
Get a signed HTTPS POST when calls start and end, texts arrive, contacts change or AI recaps are ready. Event types, payloads, signature checks and retries.
Webhooks send an HTTPS POST to your endpoint when something happens in Dialstitch, so your systems don't have to poll the API.
Add an endpoint
Open webhooks
Go to Admin, then API & Webhooks, and open the Webhooks tab.
Enter the URL
Under Add endpoint, enter your URL. It must be https on the standard port, on a public host, with no username or password in it.
Pick events
Tick the events you want. Add a Description if it helps you remember what the endpoint is for.
Save the secret
Select Add endpoint and copy the signing secret that starts with whsec_. It's shown once.
Send a test
Select Send test event. Dialstitch posts a signed test.ping event right away and logs the result.
You can also manage endpoints with the API, using the webhooks:manage scope. See Manage webhooks with the API.
Events
call.started: a call starts. An inbound call reaches a number, or an outbound call connects to the carrier.call.ended: any call ends, answered or not.call.missed: an inbound call ends without being answered.call.recapped: a call's AI transcript and summary are ready.voicemail.received: a voicemail is saved.sms.received: an inbound text arrives.sms.sent: an outbound text is handed to the carrier.contact.created: a contact is created.contact.updated: a contact is edited.disposition.logged: a disposition is logged on a call.moment.detected: an AI Moment is found in a call.
Emergency (911) calls never send webhooks.
The delivery
Each delivery is a POST with a JSON envelope:
{
"id": "5b0c9a2e-6f1d-4b7a-9c3e-2d8f1a0b4c6e",
"type": "call.ended",
"created_at": "2026-11-02T15:09:41.000Z",
"data": {
"event": "call.ended",
"occurred_at": "2026-11-02T15:09:41.000Z",
"call": {
"id": "3f1c2a9e-8d4b-4c1e-9a7f-2b6d5e8c1a40",
"direction": "inbound",
"from": "+14155550123",
"to": "+16045550188",
"remote_number": "+14155550123",
"phone_number_id": "b8e2f7c4-1d3a-4e5b-8c9d-0a1b2c3d4e5f",
"started_at": "2026-11-02T15:04:05.000Z",
"duration_seconds": 328
},
"contact": { "id": "7a9b0c1d-...", "name": "Dana Ruiz", "company": "Ruiz Plumbing" },
"user": { "id": 42, "name": "Sam Lee", "email": "sam@example.com" }
}
}id is the delivery id. It stays the same on every retry, so use it to drop duplicates. What's in data depends on the event:
- Call events and
voicemail.received:call, plus the matchingcontactand theuserwho handled it (either can benull).call.recappedaddssummary. sms.receivedandsms.sent:messagewithid,from,to,body,phone_number_idandconversation_id, pluscontactanduser.contact.createdandcontact.updated:contactwithid,name,company,notesandphones, plususer.disposition.logged:call,disposition(id,name),sub_disposition,source,contactanduser.moment.detected:call,moment(id,name,snippet),contactanduser.
Headers
X-Dialstitch-Event: the event type, such ascall.ended.X-Dialstitch-Delivery: the delivery id, the same asidin the body.X-Dialstitch-Timestamp: when this attempt was signed, in Unix seconds.X-Dialstitch-Signature:t=<unix seconds>,v1=<signature>.User-Agent:Dialstitch-Webhooks/1.0.
Verify the signature
The signature is a hex HMAC-SHA256 of <t>.<raw body>, keyed with your endpoint's secret. Check it against the raw request body before you parse the JSON, and reject timestamps more than 5 minutes off.
import crypto from "node:crypto";
export function verifyDialstitch(rawBody, header, secret) {
const parts = Object.fromEntries(
(header ?? "").split(",").map((p) => p.trim().split("=", 2)),
);
const t = Number(parts.t);
if (!Number.isInteger(t) || !parts.v1) return false;
if (Math.abs(Date.now() / 1000 - t) > 300) return false;
const expected = crypto
.createHmac("sha256", secret)
.update(`${t}.${rawBody}`)
.digest("hex");
const a = Buffer.from(expected, "hex");
const b = Buffer.from(parts.v1, "hex");
return a.length === b.length && crypto.timingSafeEqual(a, b);
}
// Express: keep the raw body for the check
app.post("/dialstitch", express.raw({ type: "application/json" }), (req, res) => {
const raw = req.body.toString("utf8");
if (!verifyDialstitch(raw, req.get("X-Dialstitch-Signature"), process.env.DIALSTITCH_WEBHOOK_SECRET)) {
return res.status(400).send("bad signature");
}
const event = JSON.parse(raw);
// handle event.type ...
res.sendStatus(200);
});The same check in Python:
import hashlib, hmac, time
def verify_dialstitch(raw_body: bytes, header: str, secret: str) -> bool:
parts = dict(p.strip().split("=", 1) for p in header.split(",") if "=" in p)
try:
t = int(parts["t"])
except (KeyError, ValueError):
return False
if abs(time.time() - t) > 300 or "v1" not in parts:
return False
expected = hmac.new(secret.encode(), f"{t}.".encode() + raw_body, hashlib.sha256).hexdigest()
return hmac.compare_digest(expected, parts["v1"])Retries
- Answer with any 2xx within 10 seconds to acknowledge a delivery. Nothing in your response body is read.
- Any other status, a timeout or a redirect counts as a failure. Redirects aren't followed, so use the final URL.
- A failed delivery is retried with backoff of 2, 4, 8, 16 and 32 minutes: 6 attempts in all.
- After 5 deliveries in a row use up every retry, the endpoint is turned off. Turn it back on from the Webhooks tab.
- Deliveries can arrive more than once and out of order. Use
idto drop duplicates andoccurred_atto order them.
Each endpoint on the Webhooks tab shows its latest deliveries, with the status, attempts and response code. Turn off pauses deliveries. Remove deletes the endpoint.
Manage webhooks with the API
These endpoints need the webhooks:manage scope.
GET /webhooks: list your webhooks.POST /webhooks: add one. Sendurl,events(a list of event names) and an optionaldescription. The response includes thesecret, once.GET /webhooks/{id}: one webhook and its 25 latest deliveries.DELETE /webhooks/{id}: remove it. Returns204.
curl -X POST https://dialstitch.com/api/v1/webhooks \
-H "Authorization: Bearer dpk_..." \
-H "Content-Type: application/json" \
-d '{"url": "https://example.com/dialstitch", "events": ["call.ended", "sms.received"]}'{
"data": {
"id": "0d1e2f3a-4b5c-4d6e-8f7a-9b0c1d2e3f4a",
"url": "https://example.com/dialstitch",
"description": null,
"events": ["call.ended", "sms.received"],
"enabled": true,
"disabled_reason": null,
"created_at": "2026-11-02T17:00:00.000Z",
"secret": "whsec_..."
}
}