# Send calls to your own endpoint with a signed webhook

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

Canonical: https://ahoya.ai/docs/integrations/webhook
Updated: 2026-09-20

## What lands on the other side

Your endpoint receives a JSON POST per conversation, signed so you can prove it came from Ahoya.

## Add your endpoint

In Integrations → Custom HTTPS endpoint, paste your URL. It must be HTTPS, and it should return a 2xx status within 8 seconds.

**Note:** Integrations are available on Starter and above. A free trial can't connect them.

## Copy 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.

## Verify 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.

**Note:** Reject deliveries whose timestamp is far from now — that's what makes the signature replay-resistant rather than merely authentic.

## Send 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.
