# Webhooks

Send an HTTP POST request to your own server or automation tool every time a form receives a submission.

**Requires Team plan or higher.**

***

## Use cases

* Trigger a Zapier / Make workflow on each submission
* Notify a Slack channel
* Sync to a CRM or database
* Mint an NFT or execute on-chain logic
* Invalidate a cache or trigger a redeploy

***

## Creating a webhook

1. Open the form builder
2. Click **Settings → Webhooks**
3. Click **Add Webhook**
4. Enter your endpoint URL (must be HTTPS in production)
5. Optionally add a **secret** for signature verification
6. Click **Save**

You can add multiple webhooks per form (Team+).

***

## Webhook payload

When a form is submitted, forms.wtf sends a `POST` request with a JSON body:

```json
{
  "event": "form.submission",
  "form": {
    "id": "clxyz...",
    "title": "DAO Contributor Survey",
    "slug": "dao-survey"
  },
  "response": {
    "id": "clresponse...",
    "walletAddress": "0xabc...def",
    "ensName": "vitalik.eth",
    "submittedAt": "2026-04-01T14:32:00.000Z",
    "durationSeconds": 87,
    "answers": [
      {
        "questionId": "q_123",
        "questionLabel": "What is your role?",
        "questionType": "MULTIPLE_CHOICE",
        "value": "Developer"
      }
    ]
  }
}
```

***

## Signature verification

If you set a webhook secret, forms.wtf signs the request so you can verify it came from us.

**Header sent with every request:**

```
X-Forms-Signature: sha256=<hmac_hex>
```

**Verifying in Node.js:**

```javascript
const crypto = require('crypto');

function verifySignature(payload, secret, signature) {
  const expected = 'sha256=' + crypto
    .createHmac('sha256', secret)
    .update(payload)
    .digest('hex');
  return crypto.timingSafeEqual(
    Buffer.from(signature),
    Buffer.from(expected)
  );
}

// In your route handler:
const rawBody = req.rawBody; // the raw request body string
const sig = req.headers['x-forms-signature'];
if (!verifySignature(rawBody, YOUR_SECRET, sig)) {
  return res.status(401).send('Invalid signature');
}
```

***

## Delivery behavior

* Webhooks are sent **fire-and-forget** — form submission is not blocked if your endpoint is slow or down
* Timeout: **5 seconds** — if your endpoint doesn't respond within 5 seconds, the request is abandoned
* **No retries** — if your endpoint is down, the webhook is not re-sent
* HTTP status codes 2xx are considered success; anything else is logged as a failure

> For critical workflows, use Google Sheets sync as a reliable backup alongside webhooks.

***

## Testing your webhook

Use [webhook.site](https://webhook.site) or [ngrok](https://ngrok.com) during development to inspect incoming payloads without deploying.

1. Get a temporary URL from webhook.site
2. Add it as a webhook URL in your form
3. Submit a test response
4. See the full payload in the webhook.site inspector

***

## Disabling a webhook

Toggle the webhook off in **Form Settings → Webhooks** without deleting it. Toggle back on to re-enable.
