Beta. This endpoint is not final. Field names and response shapes can change before general availability.
Overview
A webhook subscription tells Finn where to send event notifications. You register an HTTPS endpoint, choose which events it should receive, and Finn posts a JSON payload to that URL when a matching event occurs.
Subscriptions are workspace-scoped. Every subscription you create is visible to anyone with API access to the same workspace. For the payload structure of each event, see webhook-events. For signature verification, see webhook-security. For what happens when your endpoint is down, see webhook-retries.
The subscription object
| Field | Type | Notes |
|---|---|---|
id | string | Assigned by Finn. Use it in every subsequent call. |
url | string | HTTPS only. Plain HTTP is rejected. |
events | string[] | Event types this endpoint receives. See webhook-events. |
status | string | active, paused, or disabled. |
description | string | null | Free text for your own use. |
secret | string | Signing secret. Returned in full only on create. |
created_at | string | ISO 8601 timestamp. |
updated_at | string | ISO 8601 timestamp. |
Whether the API exposes a per-subscription filter beyond events (for example, restricting delivery to a single agent or phone number) is not yet settled. Assume it does not, and filter on your own side.
Create a subscription
curl -X POST https://api.hirefinn.ai/v1/webhooks \
-H "Authorization: Bearer $FINN_API_KEY" \
-H "Content-Type: application/json" \
-H "Idempotency-Key: sub-create-2026-07-30-01" \
-d '{
"url": "https://hooks.example.com/finn",
"events": ["call.completed", "call.failed"],
"description": "Production CRM sync"
}'
{
"id": "whsub_9f2c41a8",
"url": "https://hooks.example.com/finn",
"events": ["call.completed", "call.failed"],
"status": "active",
"description": "Production CRM sync",
"secret": "whsec_4bd2a09c7f1e4b3d8a6c5e2f0b7d9a13",
"created_at": "2026-07-30T09:14:02Z",
"updated_at": "2026-07-30T09:14:02Z"
}
The secret is returned in full in this response and nowhere else. Store it before you discard the response body. Later reads return a truncated form. If you lose it, rotate rather than guess.
Creation does not confirm your endpoint works. Finn does not perform a handshake or challenge request, so a typo in the host produces a subscription that looks healthy and delivers nothing until deliveries start failing. Send a real event and confirm receipt before treating the subscription as live. See local-development for tunnelling to a local server.
Pass an Idempotency-Key so a retried create does not produce two subscriptions pointing at the same URL. See api-idempotency.
List subscriptions
curl https://api.hirefinn.ai/v1/webhooks \
-H "Authorization: Bearer $FINN_API_KEY"
The response is a paginated collection following the conventions in api-pagination. Do not assume ordering is stable across pages while the API is in beta.
Retrieve, update, delete
# Retrieve
curl https://api.hirefinn.ai/v1/webhooks/whsub_9f2c41a8 \
-H "Authorization: Bearer $FINN_API_KEY"
# Update the event list and pause delivery
curl -X PATCH https://api.hirefinn.ai/v1/webhooks/whsub_9f2c41a8 \
-H "Authorization: Bearer $FINN_API_KEY" \
-H "Content-Type: application/json" \
-d '{"events": ["call.completed"], "status": "paused"}'
# Delete
curl -X DELETE https://api.hirefinn.ai/v1/webhooks/whsub_9f2c41a8 \
-H "Authorization: Bearer $FINN_API_KEY"
PATCH replaces the events array wholesale. It does not merge. Sending {"events": ["call.failed"]} on a subscription that also listened for call.completed silently stops the completed events. Read the current value first, then send the full list you want.
Deleting a subscription drops events already queued for it. Pause first, drain what you need, then delete.
Registering in TypeScript
const FINN_API_KEY = process.env.FINN_API_KEY!;
async function createSubscription(url: string, events: string[]) {
const res = await fetch("https://api.hirefinn.ai/v1/webhooks", {
method: "POST",
headers: {
Authorization: `Bearer ${FINN_API_KEY}`,
"Content-Type": "application/json",
"Idempotency-Key": `sub-${url}-v1`,
},
body: JSON.stringify({ url, events }),
});
if (!res.ok) {
throw new Error(`Finn returned ${res.status}: ${await res.text()}`);
}
const sub = await res.json();
console.log("Store this secret now:", sub.secret);
return sub;
}
createSubscription("https://hooks.example.com/finn", ["call.completed"]);
Errors
| Status | Cause |
|---|---|
400 | Malformed body, non-HTTPS url, or an unrecognized event name. |
401 | Missing or invalid Authorization header. See authentication. |
404 | No subscription with that ID in this workspace. |
409 | Idempotency key reused with a different body. |
429 | Rate limited. See api-rate-limits. |
Error bodies follow the shape in api-errors. An unrecognized event name fails the whole request rather than being dropped from the list, so a typo in one entry rejects the others with it.
Failure and disabling
Finn retries failed deliveries on a schedule described in webhook-retries. A subscription that keeps failing can move to disabled, at which point deliveries stop and are not backfilled when you re-enable it. Poll the subscription status, or read events from api-calls, rather than treating the webhook as the only path for data you cannot lose.