Integrations
Send calls to your own endpoint with a signed webhook
8 min · updated Sep 20, 2026
In short
Ahoya POSTs a signed JSON body to any HTTPS URL you control after every conversation, with an HMAC signature you can verify and automatic retries for about a day.
What lands where: Your endpoint receives a JSON POST per conversation, signed so you can prove it came from Ahoya.
01Add your endpoint
In Integrations → Custom HTTPS endpoint, paste your URL. It must be HTTPS, and it should return a 2xx status within 8 seconds.
Integrations are available on Starter and above. A free trial can't connect them.
02Copy the signing secret
Ahoya generates a signing secret when you save. Copy it and store it as an environment variable on your side — it's shown so you can verify deliveries.
03Verify the signature
Each delivery carries x-superagent-signature: t=<unix-seconds>,v1=<hex>. Recompute HMAC-SHA256 over the string <t>.<raw request body> using your secret, and compare with a timing-safe check before trusting the payload. Use the raw body, not a re-serialised object.
Reject deliveries whose timestamp is far from now — that's what makes the signature replay-resistant rather than merely authentic.
04Send a test event
Use Send test event to fire a sample payload at your endpoint, then check the delivery status on the card.
Frequently asked questions
- What does the payload look like?
- A JSON body of { event: "call.completed", data: { … } }. The data object carries id, channel, vapiCallId, orgId, fromNumber, toNumber, startedAt, endedAt, durationSeconds, outcome, intents, summary, recordingUrl and metadata.
- Does it fire for web chats too?
- Yes. The event name stays "call.completed" for every channel so existing endpoints keep working, and the data.channel field tells you which it was — "phone" or "web_chat". A web chat has no fromNumber.
- What happens if my endpoint is down?
- Ahoya retries after 1 minute, 10 minutes, 1 hour, 4 hours and 18 hours — six attempts over about a day. Server errors, 429s, 408s and network failures are retried; other 4xx responses are treated as permanent.
- Why is my localhost URL rejected?
- Deliveries only go to public addresses, and redirects aren't followed. That stops a webhook being pointed at internal infrastructure.