> ## Documentation Index
> Fetch the complete documentation index at: https://docs.apps.filed.com/llms.txt
> Use this file to discover all available pages before exploring further.

# Receive Webhooks

> Configure workspace webhook endpoints, verify Svix signatures, and process Filed task status events safely

Filed sends workspace-scoped task status events to HTTPS endpoints you configure.
Use webhooks when your integration needs status changes without polling the
[`tasks`](/apis/tasks) API.

Each delivery includes the Filed workspace ID, client ID, and task ID. Use all
three identifiers when matching an event to your own records. Filed uses Svix
for signing, retries, delivery history, and replay.

## Supported events

Event names are derived directly from the task status:

| Event            | Status      | Meaning                                  |
| ---------------- | ----------- | ---------------------------------------- |
| `task.running`   | `RUNNING`   | A supported task started running.        |
| `task.completed` | `COMPLETED` | A supported task completed successfully. |
| `task.failed`    | `FAILED`    | A supported task failed.                 |

These events apply only to Binder (`BINDER`), Tax Prep (`TAX_PREP`), Tax Review
(`TAX_REVIEW`), Tax Advisor (`TAX_ADVISOR`), and Tax Prep Lite
(`TAX_PREP_LITE`).

You can retrieve this information programmatically with
[`webhookEventCatalog`](/apis/webhooks#read-the-event-catalog).

## 1. Configure an endpoint

Open **Settings > Webhooks** in the Filed workspace. You must be a workspace
administrator. Add your HTTPS receiver URL, select the events you need, and
copy the endpoint signing secret.

<Warning>
  Store the signing secret in a secrets manager. Filed shows it when the endpoint
  is created or its secret is rotated. Do not put it in source control or expose
  it to browser code.
</Warning>

You can also manage endpoints through the
[`createWebhookEndpoint`](/apis/webhooks#create-an-endpoint) and related
GraphQL mutations.

## 2. Receive the payload

Every event uses the same version 1 envelope:

```json theme={null}
{
  "id": "33df4504-a6c5-5587-b231-0b56f29d0638",
  "event": "task.completed",
  "version": "1",
  "occurredAt": "2026-08-19T12:00:00.000Z",
  "workspaceId": "019f0fb2-42d9-72e0-b7ac-78b32ad45ef1",
  "clientId": "018f9c2a-3d5f-7a10-b2c4-9e8d7f6a5b4c",
  "taskId": "018f9c2b-7c4d-7e10-9a22-6b3c4d5e6f70",
  "taskType": "TAX_PREP",
  "status": "COMPLETED",
  "errorMessage": null
}
```

<ResponseField name="id" type="String!">
  A deterministic UUID for this task status event. Use the `webhook-id` header
  for delivery deduplication.
</ResponseField>

<ResponseField name="event" type="String!">
  The event name: `task.running`, `task.completed`, or `task.failed`.
</ResponseField>

<ResponseField name="version" type="String!">
  The payload schema version. The current value is `1`.
</ResponseField>

<ResponseField name="occurredAt" type="String!">
  The ISO 8601 time when the task entered the status.
</ResponseField>

<ResponseField name="workspaceId" type="ID!">
  The Filed workspace that owns the task and endpoint.
</ResponseField>

<ResponseField name="clientId" type="ID!">
  The Filed client associated with the task.
</ResponseField>

<ResponseField name="taskId" type="ID!">
  The Filed task whose status changed.
</ResponseField>

<ResponseField name="taskType" type="String!">
  One of `BINDER`, `TAX_PREP`, `TAX_REVIEW`, `TAX_ADVISOR`, or
  `TAX_PREP_LITE`.
</ResponseField>

<ResponseField name="status" type="String!">
  One of `RUNNING`, `COMPLETED`, or `FAILED`. The status always matches the event
  suffix.
</ResponseField>

<ResponseField name="errorMessage" type="String">
  The failure detail for `task.failed`. It is `null` for running and completed
  events.
</ResponseField>

## 3. Verify the signature

Verify the raw request body before parsing JSON or changing application state.
Svix signs each request with the `webhook-id`, `webhook-timestamp`, and
`webhook-signature` headers.

```typescript theme={null}
import { Webhook } from "svix";

const secret = process.env.FILED_WEBHOOK_SECRET;

export async function receiveFiledWebhook(request: Request) {
  if (!secret) throw new Error("FILED_WEBHOOK_SECRET is not configured");

  const body = await request.text();
  const payload = new Webhook(secret).verify(body, {
    "webhook-id": request.headers.get("webhook-id") ?? "",
    "webhook-timestamp": request.headers.get("webhook-timestamp") ?? "",
    "webhook-signature": request.headers.get("webhook-signature") ?? "",
  });

  return Response.json({ accepted: true }, { status: 200 });
}
```

<Warning>
  Do not verify a re-serialized JSON object. Whitespace or key ordering changes
  will invalidate the signature. Pass the exact raw request bytes to the Svix
  verification library.
</Warning>

## 4. Handle duplicates and retries

Webhook delivery is at least once. Store the `webhook-id` header in a table with
a uniqueness constraint before applying the event. If you have already handled
that ID, return a successful response without applying it again.

Return a `2xx` response promptly after durable acceptance. Move slow work to a
queue. Svix retries failed deliveries and lets workspace administrators inspect
or replay attempts from **Settings > Webhooks**.

<Tip>
  Treat events as notifications, not as the only task record. If you need to
  reconcile state, query the task by its `taskId` through the
  [`tasks`](/apis/tasks) API.
</Tip>

## 5. Rotate a signing secret

Rotate a secret from **Settings > Webhooks** or with
[`rotateWebhookEndpointSecret`](/apis/webhooks#rotate-a-signing-secret). The API
defaults to a 24-hour grace period during which both the old and new secrets are
valid.

Deploy the new secret to your receiver before the grace period ends. Remove the
old secret after you confirm deliveries are being verified with the new one.
