Webhooks
Send form data to external APIs and services
Webhooks
This is a Pro feature. Upgrade to Pro to use webhooks.
Webhooks send form submission data to any URL endpoint in real-time. Build custom integrations, sync with databases, trigger automations, and more.
How webhooks work
- User submits form
- Form data is saved locally
- HTTP POST request sent to your URL
- Your server processes the data
- Response logged for debugging
Setting up webhooks
Per-form configuration
- Edit your form
- Go to Settings → Integrations → Webhooks
- Enable webhooks
- Enter your endpoint URL
- (Optional) Add secret for verification
- Save
Endpoint URL
Your server endpoint that receives the data:
https://api.yoursite.com/webhooks/forms
https://yoursite.com/wp-json/custom/v1/form-handler
https://hooks.zapier.com/hooks/catch/123456/abcdef/Must be HTTPS in production.
Payload format
Request
POST /your-endpoint HTTP/1.1
Host: api.yoursite.com
Content-Type: application/json
X-Fodo-Event: form_submission
X-Fodo-Signature: sha256=abc123def456...
{
"event": "form_submission",
"form_id": 123,
"form_title": "Contact Form",
"entry_id": 456,
"submitted_at": "2024-01-15T14:30:00Z",
"fields": {
"name": "John Smith",
"email": "john@example.com",
"phone": "+1-555-123-4567",
"message": "I'm interested in your services..."
}
}Headers
| Header | Description |
|---|---|
Content-Type | Always application/json |
X-Fodo-Event | Event type (form_submission) |
X-Fodo-Signature | HMAC signature for verification |
User-Agent | Fodo-Forms/1.0 |
Field data
Fields are sent as key-value pairs:
{
"fields": {
"text_field": "Plain text value",
"email_field": "user@example.com",
"select_field": "selected_value",
"checkbox_field": ["option1", "option2"],
"file_field": {
"name": "document.pdf",
"url": "https://example.com/uploads/doc.pdf",
"size": 245678,
"type": "application/pdf"
}
}
}Security
Signature verification
Every request includes an HMAC-SHA256 signature. Verify to ensure the request came from Fodo Forms.
PHP Example:
<?php
$payload = file_get_contents('php://input');
$secret = 'your_webhook_secret';
$expected = 'sha256=' . hash_hmac('sha256', $payload, $secret);
$received = $_SERVER['HTTP_X_FODO_SIGNATURE'] ?? '';
if (!hash_equals($expected, $received)) {
http_response_code(401);
exit('Invalid signature');
}
$data = json_decode($payload, true);
// Process $data...Node.js Example:
const crypto = require('crypto');
app.post('/webhook', (req, res) => {
const payload = JSON.stringify(req.body);
const secret = process.env.WEBHOOK_SECRET;
const expected = 'sha256=' +
crypto.createHmac('sha256', secret)
.update(payload)
.digest('hex');
const received = req.headers['x-fodo-signature'];
if (!crypto.timingSafeEqual(
Buffer.from(expected),
Buffer.from(received)
)) {
return res.status(401).send('Invalid signature');
}
// Process req.body...
res.status(200).send('OK');
});IP allowlisting
For additional security, allowlist Fodo Forms IP addresses. Contact support for current IPs.
Response handling
Expected response
Return a 2xx status code to indicate success:
HTTP/1.1 200 OK
Content-Type: application/json
{"status": "received"}Failure response
Non-2xx responses trigger retry logic:
HTTP/1.1 500 Internal Server Error
Content-Type: application/json
{"error": "Database unavailable"}Retry logic
Failed webhook deliveries are retried automatically:
| Attempt | Delay | Total Time |
|---|---|---|
| 1st retry | 5 minutes | 5 min |
| 2nd retry | 30 minutes | 35 min |
| 3rd retry | 2 hours | 2 hr 35 min |
| Final | 6 hours | 8 hr 35 min |
After all retries fail, the webhook is marked as failed and logged.
Debugging
Webhook logs
View delivery attempts in Fodo Forms → Logs → Webhooks:
[2024-01-15 14:30:05] POST https://api.example.com/webhook
Status: 200 OK
Duration: 245ms
[2024-01-15 14:32:10] POST https://api.example.com/webhook
Status: 500 Error
Response: {"error": "Database timeout"}
Retry scheduled: 5 minutesTest webhook
Send a test payload without submitting a form:
- Go to form webhook settings
- Click Send Test
- Check your endpoint received the data
- Review the response
Request inspection
Use services to inspect webhook payloads:
Use cases
Sync to database
app.post('/webhook', async (req, res) => {
const { fields, entry_id } = req.body;
await db.insert('leads', {
entry_id,
name: fields.name,
email: fields.email,
created_at: new Date()
});
res.json({ status: 'saved' });
});Send to Slack
app.post('/webhook', async (req, res) => {
const { fields, form_title } = req.body;
await fetch(process.env.SLACK_WEBHOOK, {
method: 'POST',
body: JSON.stringify({
text: `New submission: ${form_title}`,
blocks: [
{ type: 'section', text: { type: 'mrkdwn',
text: `*${fields.name}*\n${fields.email}\n\n${fields.message}`
}}
]
})
});
res.json({ status: 'sent' });
});Create support ticket
app.post('/webhook', async (req, res) => {
const { fields } = req.body;
const ticket = await zendesk.createTicket({
subject: fields.subject,
requester: { email: fields.email, name: fields.name },
comment: { body: fields.message }
});
res.json({ ticket_id: ticket.id });
});