Webhooks
Integrate forms with external services using webhooks for real-time data delivery
Webhooks
Webhooks let you send form submissions to external services in real-time. Connect to CRMs, marketing tools, databases, Zapier, or your own API endpoints with automatic retry logic for guaranteed delivery.
What Are Webhooks?
A webhook is an HTTP POST request sent to a URL of your choice when a form is submitted. The request contains the form submission data as JSON.
Use cases:
- Send leads to your CRM (Salesforce, HubSpot, Pipedrive)
- Add subscribers to email marketing tools (Mailchimp, ConvertKit)
- Save data to databases (Airtable, Google Sheets, Notion)
- Trigger automation workflows (Zapier, Make, n8n)
- Notify your team (Slack, Discord, Microsoft Teams)
- Custom processing in your own application
Quick Start
Step 1: Get Your Webhook URL
Get a webhook URL from your service:
Zapier:
- Create a Zap with "Webhooks by Zapier" trigger
- Choose "Catch Hook"
- Copy the webhook URL
Make (formerly Integromat):
- Create a scenario with "Webhooks" module
- Choose "Custom webhook"
- Copy the webhook URL
Airtable: Use Zapier/Make as a bridge, or create an Airtable script
Your Own API: Create an endpoint that accepts POST requests:
https://yourdomain.com/api/form-submissionsStep 2: Configure Webhook in Fly Super
- Navigate to Forms → Select your form
- Click "Webhooks" tab
- Enter your webhook URL
- (Optional) Add webhook secret for security
- Click "Save"
Step 3: Test Webhook
- Click "Test Webhook" button
- Fly Super sends a test payload
- Check your endpoint received the data
- Verify data structure is correct
Step 4: Submit Real Form
- Go to your published landing page
- Fill out and submit the form
- Check webhook endpoint received data
- Verify processing worked correctly
Webhook Configuration
Webhook URL
Requirements:
- Must use HTTPS (not HTTP)
- Must be publicly accessible (not localhost)
- Should return 2xx status code for success
- Should respond within 30 seconds
Examples:
https://hooks.zapier.com/hooks/catch/123456/abcdef/
https://hook.us1.make.com/abcdefghijklmnop
https://api.yourdomain.com/webhooks/form-submissionsWebhook Secret (Optional)
A shared secret used to verify webhook authenticity:
How it works:
- You set a secret in Fly Super (e.g.,
my-secret-key-123) - Fly Super includes it in the webhook request header
- Your endpoint verifies the secret matches
- Reject requests with invalid/missing secrets
Security benefit: Prevents unauthorized parties from sending fake submissions to your endpoint.
Example verification (Node.js):
const webhookSecret = process.env.WEBHOOK_SECRET;
const requestSecret = req.headers['x-webhook-secret'];
if (requestSecret !== webhookSecret) {
return res.status(401).json({ error: 'Invalid webhook secret' });
}
// Process the submission...Webhook Payload
Request Format
HTTP Method: POST
Headers:
Content-Type: application/json
X-Webhook-Secret: your-secret-key (if configured)
User-Agent: FlySuper-Webhooks/1.0Body (JSON):
{
"submissionId": "abc12345",
"formId": "form_xyz789",
"formName": "Contact Form",
"submittedAt": "2024-01-15T10:30:00Z",
"data": {
"name": "John Smith",
"email": "john@example.com",
"company": "Acme Inc",
"message": "Interested in learning more about your product..."
},
"metadata": {
"ipAddress": "192.168.1.1",
"userAgent": "Mozilla/5.0 ...",
"landerId": "lander_abc123",
"landerSlug": "product-landing",
"referrer": "https://google.com",
"utmSource": "facebook",
"utmMedium": "cpc",
"utmCampaign": "spring-2024"
}
}Payload Fields
Top-Level Fields:
submissionId- Unique submission IDformId- Form IDformName- Form name (for your reference)submittedAt- ISO 8601 timestamp
Data Object:
- Contains all form field values
- Field names as keys (the "name" you configured)
- Values as submitted by user
Metadata Object:
ipAddress- Submitter's IP addressuserAgent- Submitter's browser/device infolanderId- Landing page ID (if submitted through landing page)landerSlug- Landing page slugreferrer- Previous page URLutmSource,utmMedium,utmCampaign- UTM tracking parameters (if present in URL)
Handling Webhooks
Response Requirements
Your endpoint must:
- Respond quickly: Under 30 seconds (preferably under 5 seconds)
- Return 2xx status: 200, 201, or 204 indicates success
- Handle errors gracefully: Return appropriate error codes
Success Response
Return 2xx status code to indicate successful processing:
// Node.js/Express example
app.post('/webhooks/form-submissions', async (req, res) => {
const submission = req.body;
try {
// Process the submission
await saveToDatabase(submission);
// Return success
res.status(200).json({ received: true });
} catch (error) {
// Return error for retry
res.status(500).json({ error: error.message });
}
});Error Handling
4xx Status Codes (Client Errors):
- No retry - Indicates permanent failure
- Use for: invalid data, authentication failures, rate limits exceeded
5xx Status Codes (Server Errors):
- Will retry - Indicates temporary failure
- Use for: database errors, third-party API failures, timeouts
Examples:
// Permanent failure - no retry
if (!submission.data.email) {
return res.status(400).json({ error: 'Email is required' });
}
// Temporary failure - will retry
try {
await saveToCRM(submission);
} catch (error) {
return res.status(500).json({ error: 'CRM temporarily unavailable' });
}Retry Logic
Fly Super automatically retries failed webhooks with exponential backoff.
Retry Schedule
| Attempt | Delay | Total Time |
|---|---|---|
| 1 (initial) | 0s | 0s |
| 2 | 30s | 30s |
| 3 | 1m | 1m 30s |
| 4 | 5m | 6m 30s |
| 5 | 15m | 21m 30s |
| 6 (final) | 1h | 1h 21m 30s |
Total retries: 5 retries over ~1.5 hours
When Retries Happen
Retries occur when:
- HTTP 5xx status code (server error)
- Request timeout (30 seconds)
- Network error (DNS failure, connection refused)
No retry when:
- HTTP 2xx status code (success)
- HTTP 4xx status code (client error - permanent failure)
Monitoring Retries
View retry status in Fly Super:
- Navigate to Submissions
- Click on a submission
- View Webhook Status:
- ✅ Success (delivered)
- ⏳ Pending (retry scheduled)
- ❌ Failed (all retries exhausted)
Common Integrations
Zapier
Setup:
- Create new Zap
- Trigger: "Webhooks by Zapier" → "Catch Hook"
- Copy webhook URL
- Add to Fly Super form
- Test with sample submission
- Add Zap actions (add to CRM, send email, etc.)
Zapier Example Actions:
- Add to Google Sheets
- Create Salesforce lead
- Send Slack notification
- Add to Mailchimp list
- Create Trello card
Make (Integromat)
Setup:
- Create new scenario
- Add "Webhooks" module → "Custom webhook"
- Copy webhook URL
- Add to Fly Super form
- Test with sample submission
- Add scenario modules (process data, send to apps)
Make Example Modules:
- Add row to Airtable
- Create HubSpot contact
- Send email via Gmail
- Update Notion database
- Post to Discord channel
Airtable
Direct Integration (Coming Soon)
Current Workaround: Use Zapier or Make to connect Fly Super → Airtable
Zap Setup:
- Trigger: Webhooks by Zapier (catch hook)
- Action: Airtable → Create Record
- Map fields:
name→ Name fieldemail→ Email fieldcompany→ Company field
Slack
Setup:
- Create Slack incoming webhook:
- Go to Slack App Directory → Incoming Webhooks
- Choose channel
- Copy webhook URL
- Add webhook URL to Fly Super form
- Format message (use webhook middleware or Zapier)
Zapier Example:
- Trigger: Webhooks by Zapier
- Action: Slack → Send Channel Message
- Message format:
New form submission from {{name}}
Email: {{email}}
Company: {{company}}
Message: {{message}}Custom API
Example Node.js Endpoint:
const express = require('express');
const app = express();
app.use(express.json());
app.post('/api/form-submissions', async (req, res) => {
const { submissionId, formName, data, metadata } = req.body;
// Verify webhook secret
const secret = req.headers['x-webhook-secret'];
if (secret !== process.env.WEBHOOK_SECRET) {
return res.status(401).json({ error: 'Unauthorized' });
}
try {
// Save to database
await db.submissions.create({
id: submissionId,
form: formName,
email: data.email,
name: data.name,
company: data.company,
message: data.message,
ip: metadata.ipAddress,
submitted_at: new Date()
});
// Send notification email
await sendEmail({
to: 'sales@yourcompany.com',
subject: `New ${formName} submission`,
body: `
Name: ${data.name}
Email: ${data.email}
Company: ${data.company}
Message: ${data.message}
`
});
// Return success
res.status(200).json({
received: true,
submissionId
});
} catch (error) {
console.error('Webhook error:', error);
// Return 500 for retry
res.status(500).json({
error: 'Processing failed',
willRetry: true
});
}
});
app.listen(3000);Testing Webhooks
Test Button
Use the "Test Webhook" button in form settings:
Test payload:
{
"submissionId": "test_123",
"formId": "form_xyz",
"formName": "Your Form Name",
"submittedAt": "2024-01-15T10:00:00Z",
"data": {
"name": "Test User",
"email": "test@example.com"
},
"metadata": {
"ipAddress": "127.0.0.1",
"userAgent": "Test",
"landerId": null,
"referrer": null
}
}What to verify:
- ✅ Endpoint received the request
- ✅ Data structure is correct
- ✅ Authentication/secret works
- ✅ Processing logic executes
- ✅ 2xx status code returned
Testing Tools
RequestBin / Webhook.site:
- Go to webhook.site
- Copy unique URL
- Add to Fly Super form webhook settings
- Submit test form
- View request in webhook.site
ngrok (Local Testing):
- Install ngrok:
npm install -g ngrok - Start your local server:
node server.js(port 3000) - Create tunnel:
ngrok http 3000 - Use ngrok URL as webhook URL
- Submit test form
- See requests hit your local server
Postman:
- Create mock webhook endpoint
- Test payload structure
- Verify response handling
Best Practices
Security
- Always use HTTPS: Never plain HTTP
- Validate webhook secret: Check
X-Webhook-Secretheader - Sanitize input: Clean/validate data before using
- Rate limiting: Prevent abuse on your endpoint
- IP whitelist: (Optional) Only accept from Fly Super IPs
Performance
- Respond quickly: Return 2xx ASAP, process async
- Use queues: For slow operations (send email, call APIs)
- Batch processing: If handling many submissions
- Monitor timeouts: Ensure < 30 second response
Example (async processing):
app.post('/webhook', async (req, res) => {
const submission = req.body;
// Acknowledge immediately
res.status(200).json({ received: true });
// Process asynchronously
processSubmission(submission).catch(console.error);
});
async function processSubmission(submission) {
// Slow operations here
await saveToCRM(submission);
await sendNotification(submission);
await updateAnalytics(submission);
}Reliability
- Handle retries gracefully: Use idempotency keys to prevent duplicates
- Log everything: Debug issues with submission logs
- Monitor failures: Alert on webhook failures
- Fallback mechanism: Manually process if webhooks fail
Idempotency example:
app.post('/webhook', async (req, res) => {
const { submissionId } = req.body;
// Check if already processed
const existing = await db.submissions.findOne({ submissionId });
if (existing) {
return res.status(200).json({ received: true, duplicate: true });
}
// Process new submission
await processSubmission(req.body);
res.status(200).json({ received: true });
});Error Handling
- Distinguish permanent vs temporary failures
- Return appropriate status codes
- Log errors with context
- Set up alerts for repeated failures
Troubleshooting
"Webhook Failed" Error
Check:
- ✅ URL is correct and HTTPS
- ✅ Endpoint is publicly accessible (not localhost)
- ✅ Endpoint returns 2xx status code
- ✅ Endpoint responds within 30 seconds
- ✅ No authentication issues (check secret)
Test:
- Use webhook.site to confirm payload format
- Use curl to test your endpoint manually:
curl -X POST https://your-endpoint.com/webhook \
-H "Content-Type: application/json" \
-H "X-Webhook-Secret: your-secret" \
-d '{"test": "data"}'"Timeout" Error
Causes:
- Endpoint takes > 30 seconds to respond
- Slow database queries
- External API calls taking too long
Solutions:
- Respond immediately, process asynchronously
- Optimize slow queries
- Use background jobs for external calls
"Retries Exhausted"
Means:
- Webhook failed 6 times (initial + 5 retries)
- No more automatic retries will happen
Next steps:
- Check endpoint logs for errors
- Fix the issue
- Manually replay the submission (coming soon)
"Invalid Secret"
Causes:
- Webhook secret doesn't match
- Header not being read correctly
Solutions:
- Verify secret in Fly Super settings
- Check header name:
x-webhook-secret(lowercase) - Log received headers to debug
Advanced Topics
Webhook Signatures (Coming Soon)
Cryptographically sign webhooks for enhanced security:
X-Webhook-Signature: sha256=abc123...Verify signature to ensure webhook is from Fly Super.
Webhook Transformations (Coming Soon)
Transform payload before sending to endpoint:
- Map field names
- Format values
- Filter fields
- Add custom data
Multiple Webhooks (Coming Soon)
Send to multiple endpoints:
- Primary CRM
- Backup database
- Analytics platform
- Notification service
What's Next?
- Submissions - View and manage form submissions
- Creating Forms - Build forms with AI or manually
- Field Types - Complete field reference
Quick Reference
| Setting | Purpose | Required |
|---|---|---|
| Webhook URL | Endpoint to receive submissions | Yes |
| Webhook Secret | Shared secret for verification | No (recommended) |
| Timeout | Max response time | 30 seconds |
| Retries | Failed delivery attempts | 5 retries |
| Retry Schedule | Delay between retries | 30s, 1m, 5m, 15m, 1h |
| Status Code | Meaning | Retry? |
|---|---|---|
| 2xx | Success | No |
| 4xx | Client error (permanent) | No |
| 5xx | Server error (temporary) | Yes |
| Timeout | No response in 30s | Yes |
Pro Tip: Start with webhook.site to understand the payload structure, then build your actual endpoint. Test thoroughly with the test button before going live!