Webhooks
Webhooks notify your server when submissions arrive. Configure a URL and Atlas will POST submission data to it in real-time.
Configuration
https://yourserver.com/webhook)You can also configure webhooks at the project level to receive all submissions.
Webhook Payload
{
"event": "submission.created",
"timestamp": "2024-01-15T10:30:00.000Z",
"form": {
"id": "form-uuid",
"public_id": "abc123xyz",
"slug": "contact",
"name": "Contact Form"
},
"submission": {
"id": "550e8400-e29b-41d4-a716-446655440000",
"data": {
"name": "John Doe",
"email": "john@example.com",
"message": "Hello!"
},
"files": [
{
"id": "file-uuid",
"field": "attachment",
"filename": "document.pdf",
"type": "application/pdf",
"size": 102400
}
],
"metadata": {
"ip": "192.168.1.1",
"user_agent": "Mozilla/5.0...",
"submitted_from": "https://example.com/contact"
},
"created_at": "2024-01-15T10:30:00.000Z"
}
}Webhook Headers
Each webhook request includes these headers:
| Header | Description |
|---|---|
Content-Type | application/json |
X-Webhook-Signature | HMAC signature for verification |
X-Webhook-Attempt | Attempt number (1, 2, 3...) |
X-Submission-Id | The submission ID |
Signature Verification
Webhooks include an HMAC-SHA256 signature to verify authenticity. Always verify signatures in production.
Generate a Webhook Secret
Verify the Signature
const crypto = require('crypto');
function verifyWebhook(payload, signature, secret) {
const expected = 'sha256=' + crypto
.createHmac('sha256', secret)
.update(payload)
.digest('hex');
return crypto.timingSafeEqual(
Buffer.from(signature),
Buffer.from(expected)
);
}
// Express.js example
app.post('/webhook', express.raw({ type: 'application/json' }), (req, res) => {
const signature = req.headers['x-webhook-signature'];
const payload = req.body.toString();
if (!verifyWebhook(payload, signature, process.env.WEBHOOK_SECRET)) {
return res.status(401).send('Invalid signature');
}
const data = JSON.parse(payload);
console.log('New submission:', data.submission.id);
res.status(200).send('OK');
});Python Example
import hmac
import hashlib
def verify_webhook(payload: bytes, signature: str, secret: str) -> bool:
expected = 'sha256=' + hmac.new(
secret.encode(),
payload,
hashlib.sha256
).hexdigest()
return hmac.compare_digest(signature, expected)PHP Example
function verifyWebhook($payload, $signature, $secret) {
$expected = 'sha256=' . hash_hmac('sha256', $payload, $secret);
return hash_equals($expected, $signature);
}Retry Behavior
If your server doesn't respond with a 2xx status code, Atlas retries with exponential backoff:
| Attempt | Delay |
|---|---|
| 1 | Immediate |
| 2 | 1 minute |
| 3 | 5 minutes |
| 4 | 30 minutes |
| 5 | 2 hours |
After 5 failed attempts, the webhook is marked as failed. You can manually retry from the dashboard.
Response Requirements
Your webhook endpoint should:
2xx status code (200, 201, 204)Wait for Confirmation
For file uploads, you may want to confirm the webhook was received before showing success to the user.
Enable Wait for webhook confirmation in form settings. When enabled:
?webhook=delivered)If webhook fails, user still gets redirected but with ?webhook=pending.
Testing Webhooks
Using ngrok
ngrok http 3000
# Use the ngrok URL as your webhook URLUsing RequestBin
Manual Retry
From the submission detail page in your dashboard, click Retry Webhook to resend.
Accessing Files
Files in the webhook payload include IDs, not URLs. To download files:
curl "https://atlasforms.app/api/v1/files/{file_id}?token={access_token}" \
-H "Authorization: Bearer fep_your_api_key"Or generate a signed URL from the API.