# Outbound feedback webhooks

POST every new thread to a URL you control — for Zapier, Make, n8n, or your own service.

Source: https://docs.annot8.app/docs/integrations/webhooks



**Pro and above.** The webhook integration is the general-purpose escape hatch:
whenever feedback arrives, Annot8 sends a JSON `POST` to a URL you own.

## Connect [#connect]

**Integrations** in the sidebar → **Webhook** → enter your endpoint URL →
**Connect**, then **Test** to send a real delivery.

It's on for every project straight away. Switch it off under **Project →
Integrations** for any project that shouldn't use it.

## Payload [#payload]

```json
{
  "event": "new_feedback",
  "projectId": "j57e8...",
  "projectName": "Marketing site",
  "threadId": "k91a2...",
  "authorName": "Sam Rivera",
  "content": "The pricing table overflows on mobile.",
  "url": "https://example.com/pricing",
  "timestamp": 1756400000000
}
```

| Field         | Type   | Notes                                     |
| ------------- | ------ | ----------------------------------------- |
| `event`       | string | Currently always `new_feedback`           |
| `projectId`   | string | The project's internal id                 |
| `projectName` | string | Human-readable name                       |
| `threadId`    | string | Use it to build a dashboard link          |
| `authorName`  | string | Display name of whoever left the feedback |
| `content`     | string | The comment body                          |
| `url`         | string | The page the feedback was left on         |
| `timestamp`   | number | Unix epoch milliseconds                   |

Sent as `Content-Type: application/json`.

Build a dashboard link from the ids:

```
https://www.annot8.app/dashboard/projects/{projectId}/{threadId}
```

## Receiving it [#receiving-it]

```ts title="app/api/annot8/route.ts"
export async function POST(req: Request) {
  const payload = await req.json();

  if (payload.event !== "new_feedback") {
    return new Response(null, { status: 204 });
  }

  await notifyOnCall({
    title: payload.projectName,
    body: payload.content,
    link: `https://www.annot8.app/dashboard/projects/${payload.projectId}/${payload.threadId}`,
  });

  return new Response(null, { status: 204 });
}
```

Respond with any 2xx. A non-2xx response is recorded as a delivery error on the
integration.

## With Zapier, Make, or n8n [#with-zapier-make-or-n8n]

All three offer a "catch webhook" trigger that hands you a URL. Paste that URL
into Annot8 and you're done — no code, and you can fan out from there into
anything those platforms support.

## Automations vs the webhook integration [#automations-vs-the-webhook-integration]

The webhook integration fires on **every** new thread. If you only want some of
them, either filter in your own endpoint (simplest — you already have the
payload) or use an [automation](/docs/feedback/automations) for the notify
actions and keep the webhook for the full feed.

## Security notes [#security-notes]

* **Your endpoint should be idempotent.** Use `threadId` to deduplicate;
  delivery is at-least-once in spirit, not exactly-once.
* **The payload isn't signed.** Treat the URL itself as the secret: use a long,
  unguessable path, keep it HTTPS, and don't take irreversible action on the
  payload alone without verifying against your own data.
* **Don't log the whole payload indiscriminately.** `content` is user-written
  text and can contain anything a reviewer typed.
