Webhooks
Webhooks allow you to receive form submission data in real-time by sending HTTP POST requests to your server or any third-party service.
Overview
When a form is submitted, Axiforms sends a standardized JSON payload to your configured webhook URL. This enables you to:
- Process form data in your own backend
- Trigger custom workflows and automations
- Integrate with any service that accepts webhooks
- Build custom integrations
Setting Up Webhooks
- Navigate to your form's Settings → Integrations
- Click on Webhooks
- Enable the integration
- Enter your webhook URL (e.g.,
https://your-server.com/webhook) - Optionally configure:
- Secret Key: For HMAC signature verification
- Custom Headers: Additional HTTP headers
- Timeout: Request timeout (default: 30 seconds)
- Retries: Number of retry attempts (default: 3)
- Click Create Integration
Webhook Payload
All webhooks receive a standardized JSON payload:
{
"event": "form.submitted",
"projectId": "clx123...",
"responseId": "clx456...",
"submittedAt": "2024-01-15T10:30:00.000Z",
"metadata": {
"ip": "192.168.1.1",
"userAgent": "Mozilla/5.0...",
"referer": "https://example.com"
},
"responses": [
{
"blockId": "block_123",
"blockType": "text",
"label": "Full Name",
"value": "John Doe"
},
{
"blockId": "block_456",
"blockType": "email",
"label": "Email Address",
"value": "john@example.com"
}
],
"form": {
"id": "clx123...",
"name": "Contact Form",
"slug": "contact-form"
}
}
Note: All integrations (Webhooks, Zapier, n8n) receive the same standardized payload format.
Payload Fields
| Field | Type | Description |
|---|---|---|
event | string | Always "form.submitted" |
projectId | string | Unique identifier for the form |
responseId | string | Unique identifier for this submission |
submittedAt | string | ISO 8601 timestamp of submission |
metadata | object | Submission metadata (IP, user agent, etc.) |
responses | array | Array of field responses |
form | object | Form information (id, name, slug) |
Response Object
Each item in the responses array contains:
| Field | Type | Description |
|---|---|---|
blockId | string | Unique identifier for the form field |
blockType | string | Type of form field (e.g., text, email, select) |
label | string | Field label (if available) |
value | any | Field value (type depends on field type) |
Security: HMAC Signatures
To verify that webhook requests are coming from Axiforms, you can enable HMAC signature verification:
- Set a Secret Key in your webhook configuration
- Axiforms will include an
X-Webhook-Signatureheader in all requests - Verify the signature on your server
Signature Format
X-Webhook-Signature: sha256=<hex_signature>
Verification Example (Node.js)
const crypto = require("crypto");
function verifySignature(payload, signature, secret) {
const hmac = crypto.createHmac("sha256", secret);
const computedSignature = hmac.update(JSON.stringify(payload)).digest("hex");
const expectedSignature = signature.replace("sha256=", "");
return crypto.timingSafeEqual(
Buffer.from(computedSignature),
Buffer.from(expectedSignature)
);
}
// In your webhook handler
app.post("/webhook", express.json(), (req, res) => {
const signature = req.headers["x-webhook-signature"];
const secret = process.env.WEBHOOK_SECRET;
if (!verifySignature(req.body, signature, secret)) {
return res.status(401).send("Invalid signature");
}
// Process webhook...
res.status(200).send("OK");
});
Verification Example (Python)
import hmac
import hashlib
def verify_signature(payload, signature, secret):
computed = hmac.new(
secret.encode(),
json.dumps(payload).encode(),
hashlib.sha256
).hexdigest()
expected = signature.replace('sha256=', '')
return hmac.compare_digest(computed, expected)
# In your webhook handler
@app.route('/webhook', methods=['POST'])
def webhook():
signature = request.headers.get('X-Webhook-Signature')
secret = os.environ['WEBHOOK_SECRET']
if not verify_signature(request.json, signature, secret):
return 'Invalid signature', 401
# Process webhook...
return 'OK', 200
Custom Headers
You can add custom HTTP headers to webhook requests:
{
"Authorization": "Bearer your-token",
"X-Custom-Header": "custom-value"
}
These headers will be included in every webhook request.
Retry Logic
If a webhook request fails (non-2xx status code or timeout), Axiforms will automatically retry:
- Default retries: 3 attempts
- Backoff: Exponential backoff (1s, 2s, 4s)
- Timeout: 30 seconds per request (configurable)
Webhooks are retried asynchronously and won't block form submissions.
Testing Webhooks
Use the Test Webhook button in the integration settings to send a test payload:
{
"event": "form_submission_test",
"id": "test_response_id_123",
"timestamp": "2024-01-15T10:30:00.000Z",
"form": {
"id": "your-project-id",
"name": "Test Form",
"slug": "test-form"
},
"metadata": {
"ip": "127.0.0.1",
"userAgent": "Test Agent",
"country": "US",
"city": "Testville"
},
"responses": [
{
"blockId": "test_field_1",
"label": "Name",
"type": "text",
"value": "John Doe"
},
{
"blockId": "test_field_2",
"label": "Email",
"type": "email",
"value": "john.doe@example.com"
}
]
}
Best Practices
- Always verify signatures when processing sensitive data
- Handle timeouts gracefully - Axiforms retries automatically, but your server should respond quickly
- Idempotency - Design your webhook handler to handle duplicate submissions (use
responseId) - Error handling - Return appropriate HTTP status codes:
200-299: Success400-499: Client error (won't retry)500+: Server error (will retry)
- Logging - Log all webhook requests for debugging and auditing
Troubleshooting
Webhook not receiving data
- Check that the webhook URL is accessible from the internet
- Verify the URL is correct and uses
https:// - Check your server logs for incoming requests
- Use the Test Webhook feature to verify connectivity
Signature verification failing
- Ensure the secret key matches exactly (no extra spaces)
- Verify you're using the raw request body (not parsed JSON) for signature calculation
- Check that you're comparing signatures securely (use
timingSafeEqualorcompare_digest)
Timeout errors
- Increase the timeout in webhook settings (max 60 seconds)
- Optimize your webhook handler to respond quickly
- Consider processing asynchronously and returning immediately