# Webhooks

> Page: https://docs.flung.dev/webhooks. Index of every page: https://docs.flung.dev/llms.txt

Have Flung tell your server when posts publish or fail and when accounts need a person, instead of asking again and again.

## TL;DR

| Question | Answer |
|---|---|
| How do I start? | Register an https endpoint and keep the `whsec_` secret it returns. |
| How do I know an event is from Flung? | Verify its signature. Flung signs to the Standard Webhooks spec, so the SDK or any Standard Webhooks library checks it. |
| What if my endpoint is down? | Flung tries again, for a while, with growing gaps. Each event keeps its id, so drop ids you have already handled. |
| Can I rotate the secret? | Yes. Flung signs with both secrets while you switch, and the SDK accepts either. |

## Register an endpoint

**curl**

```bash
curl -X POST 'https://api.flung.dev/v1/webhook-endpoints' \
  --header "Authorization: Bearer $FLUNG_API_KEY" \
  --header 'Content-Type: application/json' \
  --header "Idempotency-Key: $(uuidgen)" \
  --data '{
  "url": "https://example.com/webhooks/flung",
  "events": [
    "post.published",
    "post.failed"
  ]
}'
```

**TypeScript**

```ts
import { Flung } from '@flung.dev/sdk'

const flung = new Flung({ apiKey: process.env.FLUNG_API_KEY! })
const result = await flung.webhookEndpoints.create({
  url: 'https://example.com/webhooks/flung',
  events: [
    'post.published',
    'post.failed',
  ],
})
```

**fetch**

```js
const response = await fetch('https://api.flung.dev/v1/webhook-endpoints', {
  method: 'POST',
  headers: {
    'Authorization': `Bearer ${process.env.FLUNG_API_KEY}`,
    'Content-Type': 'application/json',
    'Idempotency-Key': crypto.randomUUID(),
  },
  body: JSON.stringify({
    url: 'https://example.com/webhooks/flung',
    events: [
      'post.published',
      'post.failed',
    ],
  }),
})
if (!response.ok) throw new Error((await response.json()).error.message)
const result = await response.json()
```

## Verify each event

Read the raw body as text: parsing it and writing it out again changes the bytes and breaks the signature.

```ts
export async function POST(request: Request): Promise<Response> {
  const body = await request.text()
  const event = await verifyWebhook({ body, headers: request.headers, secret: process.env.FLUNG_WEBHOOK_SECRET! })
  if (event.type === 'post.failed') console.warn(`Post ${event.data.post.id} failed`)
  return new Response(null, { status: 204 })
}
```

## Events

| Event |
|---|
| `post.published` |
| `post.partially_published` |
| `post.failed` |
| `target.published` |
| `target.failed` |
| `account.connected` |
| `account.disconnected` |
| `account.token_refresh_failed` |
| `account.reconnect_required_soon` |
| `inbox.received` |

Every event has an `id`, a `type`, a `createdAt` time and `data` with the post or the account it is about.
