Overview
Add a webhook endpoint, and Finn sends a signed call.completed event to it once for each of your organization's calls after the call ends, answered or not. Webhooks push results to you. To pull a result, call GET /api/v1/calls/{call_uuid} (see api-calls).
Use both. The webhook tells you as soon as a call is done. The calls endpoint lets you recover anything your receiver missed.
Managing endpoints
Add, view and delete endpoints in the dashboard under Settings → Integrations → Webhooks. You can't manage endpoints with an API key, and there's no /api/v1 route for it.
Add an endpoint
Click Add endpoint and enter the URL. Each endpoint is subscribed to call.completed, the only event Finn sends.
After you save, the dashboard shows the endpoint's signing secret once. It starts with whsec_. Copy it into your secrets manager right away. You need it to verify signatures, and Finn can't show it again. If you lose it, delete the endpoint and add it again.
An organization can have up to 5 endpoints.
A new endpoint receives calls that end after you add it. Finn doesn't backfill older calls; read those with GET /api/v1/calls/{call_uuid}.
URL rules
Finn checks the URL when you add the endpoint and again before every delivery. It rejects:
- any URL that doesn't use
https - a host name that doesn't resolve
- a host that resolves to a loopback, link-local (
169.254.0.0/16, which includes cloud metadata services), private (10.0.0.0/8,172.16.0.0/12,192.168.0.0/16), multicast or unspecified address, or the IPv6 equivalents
These rules stop a webhook from making Finn's servers send requests to internal addresses. If your endpoint's DNS later starts resolving to a blocked address, deliveries fail and aren't retried.
Delete an endpoint
Delete an endpoint from its row in the list. Finn stops sending to it right away, and the endpoint's delivery history is deleted too.
You can't pause, edit or disable an endpoint. To change the URL, add a new endpoint and then delete the old one.
Deliveries
Click Deliveries on an endpoint to see its 100 most recent deliveries. Each one shows the status (pending, delivered or failed), the number of attempts, the HTTP status your server returned and any error. Start here when your receiver stops getting events.
The call.completed payload
{
"event": "call.completed",
"created_at": "2026-09-18T11:14:09.000Z",
"data": {
"call_uuid": "3f2504e0-4f89-11d3-9a0c-0305e82c3301",
"finn_id": "8f14e45f-ceea-467a-9f6a-1c0e5b2a77d1",
"deployment_id": "b2d4a611-99c7-42f0-8a3e-1f5d6c0e9a72",
"status": "completed",
"disconnect_reason": "hangup",
"direction": "outbound",
"from_number": "+14085550199",
"to_number": "+14155550142",
"duration_seconds": 97,
"user_sentiment": "positive",
"call_successful": true,
"created_at": "2026-09-18T11:12:31.000Z",
"updated_at": "2026-09-18T11:14:08.000Z",
"recording_url": "https://storage.example.com/recordings/3f2504e0-4f89-11d3-9a0c-0305e82c3301.wav?sig=...",
"analysis": [
{
"question_name": "interested",
"question_type": "Yes/No",
"answer": "Yes",
"needs_review": false,
"reasoning": "Caller asked for a follow-up on Tuesday.",
"extracted_at": "2026-09-18T11:14:07.000Z"
}
]
}
}
webhook-post-call describes every field. deployment_id is the same id that GET /api/v1/deployments returns, so you can join the event to its deployment; it's null for calls that don't belong to a deployment, such as calls placed with POST /api/v1/calls. For an answered call on a Finn with post-call analysis fields, the event waits until the analysis is saved, so analysis is normally filled in. If the analysis hasn't arrived after about an hour, the event is sent with whatever exists, which can be an empty array. Calls that weren't answered, and calls on a Finn without analysis fields, are usually sent within about 5 minutes of the call ending. See webhooks for timing. GET /calls/{call_uuid}, by contrast, can return a call before analysis has run.
recording_url is a signed link that works for 1 hour after the event is sent. Download the file when you receive the event, or call GET /api/v1/calls/{call_uuid} later for a fresh link. It's null when the call has no recording.
Delivery behavior
| Property | Value |
|---|---|
| Method | POST, Content-Type: application/json |
| Timeout | 10 seconds per attempt |
| Attempts | Up to 3 |
| Wait between attempts | 1 second, then 2 seconds |
| Retried | Timeouts, connection errors, 429, 5xx, 3xx, and any response whose body is larger than 64 KB |
| Not retried | 2xx (success), any other 4xx, and URLs that fail the rules above |
| Redirects | Not followed |
Finn treats any 4xx except 429 as a permanent rejection. It doesn't read your response body, but a body over 64 KB fails the attempt, so return a short one. Respond 2xx as soon as you've safely stored the event, and do the rest of the work afterward. Anything you do before responding counts toward the 10-second timeout.
Each endpoint gets at most one call.completed per call. Finn records each delivery before sending, so you're never notified twice about the same call. The flip side: if all 3 attempts fail, Finn never sends that event again. Recover it with GET /api/v1/calls/{call_uuid}. See webhook-retries.
Headers
| Header | Value |
|---|---|
X-Finn-Signature | t=<unix seconds>,v1=<hex HMAC-SHA256> |
X-Finn-Event | The event name, call.completed |
User-Agent | Finn-Webhooks/1 |
The signature is calculated over <t>.<raw body> using the endpoint's secret. Verify it against the raw bytes of the body, before you parse the JSON. webhook-security has the full steps.
Not available yet
- Events other than
call.completed - Managing endpoints with an API key or through
/api/v1 - Pausing, editing or rotating the secret of an existing endpoint
- Replaying a failed delivery. Use
GET /api/v1/calls/{call_uuid}to fill gaps. - Disabling an endpoint automatically when it keeps failing. Finn counts failures but doesn't act on the count.