Skip to main content

API reference

Webhook subscriptions

Registering and managing endpoints.

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

FieldTypeNotes
idstringAssigned by Finn. Use it in every subsequent call.
urlstringHTTPS only. Plain HTTP is rejected.
eventsstring[]Event types this endpoint receives. See webhook-events.
statusstringactive, paused, or disabled.
descriptionstring | nullFree text for your own use.
secretstringSigning secret. Returned in full only on create.
created_atstringISO 8601 timestamp.
updated_atstringISO 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

StatusCause
400Malformed body, non-HTTPS url, or an unrecognized event name.
401Missing or invalid Authorization header. See authentication.
404No subscription with that ID in this workspace.
409Idempotency key reused with a different body.
429Rate 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.