# Formbricks Webhooks

[Formbricks](https://formbricks.com) is an open-source (self-hostable) survey and experience-management platform. Each environment can have webhooks that POST a JSON event whenever a survey response is created, updated or finished. Deliveries follow Standard Webhooks, so each carries `webhook-id`, `webhook-timestamp` and `webhook-signature` headers.

## When to Use This Skill

* How do I receive Formbricks webhooks?
* How do I verify a Formbricks webhook signature (`webhook-signature`)?
* Where do I find my Formbricks webhook secret (`whsec_...`)?
* How do I handle `responseCreated`, `responseUpdated` or `responseFinished` events?
* Why is my Formbricks webhook verification failing, or failing only on retries and replays?
* Why does my Formbricks webhook arrive without a `webhook-signature` header?

## Signing Requirement

Hookdeck's configuration alert for this source reads: "Webhooks created before Formbricks added signing have no secret and are unsigned. If yours has no secret, create a new webhook." The `webhook-signature` header is sent only if a secret is configured. New webhooks always get a secret, so create a new webhook if yours has none. The examples reject deliveries without a signature (400) and fail closed (500) when the secret env var is unset.

## Verification (core)

| Header | Value |
| --- | --- |
| `webhook-id` | Identifies the message; retries reuse it. Your idempotency key (the body has no event id) |
| `webhook-timestamp` | Unix seconds |
| `webhook-signature` | One or more space-separated `v1,<base64>` entries |

Signed content is `{webhook-id}.{webhook-timestamp}.{raw body}`, HMAC-SHA256, base64 output. The key is the base64-**decoded** part of the `whsec_...` secret after the prefix. Compare against every `v1,` entry; ignore other versions. If `webhook-id` is absent, read `svix-id`, `svix-timestamp` and `svix-signature` instead, as Hookdeck does. The examples use the `standardwebhooks` library (npm and PyPI):

```javascript
const { Webhook } = require('standardwebhooks');

// rawBody: exact request body (Buffer or string), NOT re-serialised JSON
function verifyFormbricks(rawBody, headers, secret = process.env.FORMBRICKS_WEBHOOK_SECRET) {
  const p = headers['webhook-id'] ? 'webhook' : 'svix'; // fall back to the svix-* names
  new Webhook(secret).verify(rawBody, {
    'webhook-id': headers[`${p}-id`],
    'webhook-timestamp': headers[`${p}-timestamp`],
    'webhook-signature': headers[`${p}-signature`],
  }, { jsonParse: false }); // throws on a bad signature or stale timestamp → respond 401
}

```

The library always enforces a fixed **5-minute timestamp window**. Hookdeck retries and replays resend the original `webhook-timestamp`, so the library rejects them once they are more than 5 minutes old. If you receive through Hookdeck and need late retries to pass, use the manual verifier in [references/verification.md](https://github.com/hookdeck/webhook-skills/blob/main/skills/formbricks-webhooks/references/verification.md), which can drop or widen the window. In Express, read the body with `express.raw({ type: '*/*' })` so any content type verifies, and parse JSON only after the check.

> **For complete handlers with tests**, see [examples/express/](https://github.com/hookdeck/webhook-skills/tree/main/skills/formbricks-webhooks/examples/express/), [examples/nextjs/](https://github.com/hookdeck/webhook-skills/tree/main/skills/formbricks-webhooks/examples/nextjs/), [examples/fastapi/](https://github.com/hookdeck/webhook-skills/tree/main/skills/formbricks-webhooks/examples/fastapi/).

## Event Type and Payload

Every body has the same three top-level fields, and the event name is `event`:

```json
{
  "event": "responseFinished",
  "webhookId": "webhookId",
  "data": {
    "id": "responseId",
    "surveyId": "surveyId",
    "finished": true,
    "data": { "newsletter_consent": "accepted" },
    "survey": { "title": "Customer Satisfaction Survey", "status": "inProgress", "type": "link" }
  }
}

```

`data` is the response object: the answers are in `data.data` (keyed by question id), `data.survey` is a summary of the survey, and `data.finished` tells you whether the respondent completed it. Other fields include `contact`, `contactAttributes`, `language`, `meta`, `tags`, `ttc` and `variables`.

## Event Types

| Event | Fired when |
| --- | --- |
| `responseCreated` | A survey response is created |
| `responseUpdated` | A response gains or changes answers |
| `responseFinished` | The respondent completes the survey |

These are the three documented triggers (the list may not be exhaustive): ignore types you don't recognise. See [references/overview.md](https://github.com/hookdeck/webhook-skills/blob/main/skills/formbricks-webhooks/references/overview.md).

## Delivery Semantics

Return a `2xx` within 5 seconds (`WEBHOOK_DELIVERY_TIMEOUT_MS` when self-hosted), then process asynchronously. Network errors, timeouts, redirects and `408`, `429` or `5xx` responses are retried up to 5 attempts in all, at about 30 seconds, then 1, 2 and 4 minutes, after which the event is dropped. Any other `4xx` is not retried, so a `400` or `401` for a bad signature is final. Deduplicate on `webhook-id`, which is the same on every attempt.

## Environment Variables

```bash
FORMBRICKS_WEBHOOK_SECRET=whsec_xxxxx   # full value, prefix included; handlers fail closed (500) without it

```

## Local Development

```bash
# Express / Next.js (use 8000 for FastAPI). No install or account required.
npx hookdeck-cli listen 3000 formbricks --path /webhooks/formbricks

```

Set the printed URL as the webhook URL under Configuration > Integrations > Webhooks, and click **Test Endpoint** before saving.

## Reference Materials

* [references/overview.md](https://github.com/hookdeck/webhook-skills/blob/main/skills/formbricks-webhooks/references/overview.md) - Events, payload structure and delivery behaviour
* [references/setup.md](https://github.com/hookdeck/webhook-skills/blob/main/skills/formbricks-webhooks/references/setup.md) - Create a webhook, find the secret, self-hosting notes
* [references/verification.md](https://github.com/hookdeck/webhook-skills/blob/main/skills/formbricks-webhooks/references/verification.md) - Library and manual verification, gotchas, debugging