Ahoya

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.

  1. 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.

  2. 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.

  3. 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.

  4. 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.

See all integrations