Webhook Setup
Webhooks let WittyForm send an HTTP POST request to your server every time a form is submitted. This is the most flexible way to integrate: you control the endpoint, the processing logic, and the destination of the data.
Adding a webhook URL
- Open your form and go to Settings → Integrations → Webhooks.
- Click Add webhook.
- Enter your endpoint URL (must be HTTPS in production).
- Optionally, give the webhook a label (e.g., "CRM sync").
- Click Save.
You can add multiple webhooks per form. Each one fires independently on every submission.
Payload format
WittyForm sends JSON via HTTP POST. Field answers are keyed by your form field IDs:
{
"event": "form.submitted",
"timestamp": "2026-01-15T10:30:00.000Z",
"formId": "form-uuid",
"data": {
"formId": "form-uuid",
"responseId": "response-uuid",
"answers": {
"field-id": "Jane Smith",
"email-field-id": "[email protected]"
},
"displayAnswers": {
"field-id": "Jane Smith"
},
"submittedAt": "2026-01-15T10:30:00.000Z",
"form": { "id": "form-uuid", "title": "Contact Form" }
}
}HTTP method and headers
All webhooks use POST. These headers are included by default:
Content-Type: application/jsonUser-Agent: WittyForm-Webhook/1.0X-Webhook-Event,X-Webhook-ID, andX-Webhook-Delivery-IDX-Webhook-TimestampX-WittyForm-Webhook-Token(a stable per-webhook token for destination firewall rules)X-Webhook-Signature: sha256=...(if signing is enabled)
Custom headers
You can add custom headers in the webhook configuration panel. Click Add header and enter a key-value pair. Common uses:
X-API-Key: your-secret-key-here
Authorization: Bearer your-token-hereAuthentication
API key header
The simplest approach: add a custom header with a secret value that your server validates on every request. If the header is missing or wrong, reject the request with 401 Unauthorized.
HMAC signature verification
For stronger security, enable webhook signing. WittyForm generates an HMAC-SHA256 signature of the payload body using a shared secret and includes it in the X-Webhook-Signature header.
To verify on your server (Node.js example):
const crypto = require('crypto');
function verifySignature(payload, signature, secret) {
const expected = 'sha256=' + crypto
.createHmac('sha256', secret)
.update(payload, 'utf8')
.digest('hex');
return crypto.timingSafeEqual(
Buffer.from(signature),
Buffer.from(expected)
);
}
// In your request handler:
const isValid = verifySignature(
req.body, // raw request body string
req.headers['x-wittyform-signature'],
process.env.WITTYFORM_WEBHOOK_SECRET
);
if (!isValid) {
return res.status(401).json({ error: 'Invalid signature' });
}Retry logic
If your endpoint returns a non-2xx status code (or times out after 10 seconds), WittyForm retries the delivery:
- Attempt 1: immediate
- Attempt 2: after 1 second
- Attempt 3: after 5 seconds
- Attempt 4: after 30 seconds
- Attempt 5: after 5 minutes
- Attempt 6: after 1 hour
Once a webhook exhausts its configured retry count, it is marked as failed in the integration log. You can manually retry from the dashboard.
n8n and Cloudflare setup
- Use the n8n production URL with
/webhook/; do not save the temporary/webhook-test/URL. - Activate the n8n workflow before submitting a form.
- Use the WittyForm Send Test action to send a safe sample with your field IDs.
- For a Cloudflare-protected receiver, copy the webhook's Delivery Token and configure a WAF/rate-limit exception for
X-WittyForm-Webhook-Tokenwith an exact token match. WittyForm cannot modify the receiver's Cloudflare rules for you.
Debugging with webhook.site
Before pointing a webhook at your production server, use a debugging tool to inspect the payload:
- Go to webhook.site and copy your unique URL.
- Paste that URL as your webhook endpoint in WittyForm.
- Submit a test form entry.
- Check webhook.site to see the full request headers, body, and timing.
This is also useful for troubleshooting when your own server returns errors: compare the payload you expect with what WittyForm actually sends.
Common errors
| Error | Cause | Fix |
|---|---|---|
Connection refused | Endpoint is not reachable | Check that your server is running and the URL is correct |
SSL certificate error | Invalid or expired certificate | Renew your SSL certificate or use a valid CA |
Timeout (10s) | Server takes too long to respond | Process the webhook asynchronously: return 200 immediately, then handle the data |
401 Unauthorized | Authentication failed | Verify your API key header or HMAC secret matches |