Webhooks
Webhooks let you automatically send form submissions to your own server or third-party services like Zapier, Make, or n8n. This is a Pro feature.
How Webhooks Work
When someone submits your form, SendForm.live sends an HTTP POST request to your specified URL with the submission data as JSON.
Setting Up a Webhook
- Go to your form's Settings
- Scroll to the Webhooks section
- Enter your webhook URL (must be HTTPS)
- Optionally add a secret for signature verification
- Click Add Webhook
Webhook Payload
Each webhook request includes a JSON payload with the following structure. Note that the data object uses your field labels (like "Full Name") rather than internal IDs, making it easy to use in integrations.
{
"event": "submission.created",
"form_id": "abc123",
"submission": {
"id": "sub_xyz789",
"form_id": "abc123",
"data": {
"Full Name": "John Doe",
"Email": "john@example.com",
"Message": "Hello!"
},
"created_at": "2025-01-15T10:30:00.000Z"
},
"timestamp": "2025-01-15T10:30:01.000Z"
}Keep your field labels unique. Because data is keyed by label, two fields sharing a label collapse into one key and only the later value arrives. Fields you have since deleted from the form are not included either, even if older submissions still hold their data.
Request Headers
SendForm.live includes these headers with every webhook request:
Content-Type: application/jsonUser-Agent: SendForm-Webhook/1.0X-SendForm-Signature(if you configured a secret)
Verifying Webhook Signatures
If you add a secret to your webhook, SendForm.live signs each payload using HMAC-SHA256. This lets you verify that requests genuinely came from SendForm.live.
How to Verify
- Get the
X-SendForm-Signatureheader from the request - Compute HMAC-SHA256 of the raw request body using your secret
- Compare your computed signature with the header value
Example (Node.js)
const crypto = require('crypto');
function verifySignature(payload, signature, secret) {
const computed = 'sha256=' + crypto
.createHmac('sha256', secret)
.update(payload) // the RAW body bytes, not a re-serialised object
.digest('hex');
// timingSafeEqual throws if the buffers differ in length, so a missing or
// malformed header would become a 500 instead of a clean rejection.
if (typeof signature !== 'string' || signature.length !== computed.length) {
return false;
}
return crypto.timingSafeEqual(
Buffer.from(signature),
Buffer.from(computed)
);
}
// In your webhook handler:
const isValid = verifySignature(
req.rawBody,
req.headers['x-sendform-signature'],
process.env.WEBHOOK_SECRET
);
if (!isValid) {
return res.status(401).send('Invalid signature');
}Integration Examples
Zapier
- Create a new Zap with Webhooks by Zapier as the trigger
- Choose Catch Hook
- Copy the webhook URL Zapier provides
- Paste it in your SendForm.live webhook settings
- Submit a test form to set up the Zap
Make (Integromat)
- Create a new scenario with Webhooks module
- Choose Custom webhook
- Copy the webhook URL
- Add it to your SendForm.live form settings
Your Own Server
Create an endpoint that accepts POST requests with JSON. Make sure to:
- Use HTTPS (required)
- Return a 2xx status code on success
- Verify the signature if you configured a secret
- Process the webhook quickly (under 10 seconds)
Troubleshooting
Webhook not firing?
- Check the webhook is still listed under your form settings
- Check that your URL is valid and uses HTTPS
- Verify your server is accessible from the internet
Getting errors?
- Webhooks must return a 2xx status code within 10 seconds
- Check your server logs for incoming requests
- Ensure your endpoint accepts POST requests with JSON
Retries and duplicate deliveries
- We make up to 3 delivery attempts in total (the first try plus 2 retries), waiting 1 second and then 4 seconds between them.
- Only temporary failures are retried — a 5xx response, a 429, or a network error. A 4xx response means your endpoint rejected the request on purpose, so we don't send it again.
- Delivery is at least once. If your server processes a request but the response doesn't reach us, we'll retry and you'll receive the same submission twice. Use the
submission.idfield to ignore duplicates. - Each attempt is signed separately, so the signature differs between retries. Always verify against the body you actually received.
Signature verification failing?
- Make sure you're using the raw request body (not parsed JSON)
- Verify the secret matches exactly (no extra whitespace)
- Use constant-time comparison to prevent timing attacks
