Webhooks allow you to receive HTTP POST notifications when events occur in your database. Instead of polling for changes, you can subscribe to specific events and receive instant updates.
Event Types
Webhooks support the following event types:
Record Events
| Event | Description |
|---|
record.created | A new record was added to a table |
record.updated | An existing record was modified |
record.deleted | A record was deleted from a table |
Table Events
| Event | Description |
|---|
table.created | A new table was added to the database |
table.updated | A table’s properties (like name) were changed |
table.deleted | A table was deleted |
Field Events
| Event | Description |
|---|
field.created | A new field was added to a table |
field.updated | A field’s properties or configuration were changed |
field.deleted | A field was removed from a table |
Webhook Payload
When an event occurs, we send an HTTP POST request to your webhook URL with a JSON payload:
{
"id": "evt_a1b2c3d4-e5f6-7890-abcd-ef1234567890",
"type": "record.created",
"timestamp": "2025-01-15T10:30:00.000Z",
"data": {
"tableId": "tbl_abc123",
"recordIds": ["rec_xyz789"],
"records": [
{
"id": "rec_xyz789",
"data": {
"Name": "John Doe",
"Email": "john@example.com"
}
}
]
},
"metadata": {
"webhookId": 123,
"organizationId": 456,
"baseId": "base_def456",
"attempt": 1,
"source": "ui"
}
}
Payload Fields
| Field | Type | Description |
|---|
id | string | Unique event ID for idempotency |
type | string | The event type (e.g., record.created) |
timestamp | string | ISO 8601 timestamp of when the event occurred |
data | object | Event-specific data (see below) |
metadata.webhookId | number | ID of the webhook subscription |
metadata.organizationId | number | Your organization ID |
metadata.baseId | string | The database ID |
metadata.attempt | number | Delivery attempt number (1 for first attempt) |
metadata.source | string | Where the change originated (ui, public_api, zapier, make, import) |
Record Event Data
For record events (record.created, record.updated, record.deleted):
{
"tableId": "tbl_abc123",
"recordIds": ["rec_xyz789"],
"records": [...],
"previousRecords": [...] // Only for record.updated
}
Table Event Data
For table events (table.created, table.updated, table.deleted):
{
"tableId": "tbl_abc123",
"table": {
"id": "tbl_abc123",
"name": "Customers",
"order": 0,
"primaryFieldId": "fld_xyz"
},
"previousTable": {...} // Only for table.updated
}
Field Event Data
For field events (field.created, field.updated, field.deleted):
{
"tableId": "tbl_abc123",
"fieldId": "fld_xyz789",
"field": {
"id": "fld_xyz789",
"name": "Email",
"type": "email",
"order": 1
},
"previousField": {...} // Only for field.updated
}
Verifying Webhook Signatures
Each webhook request includes an X-Webhook-Signature header containing an HMAC-SHA256 signature. Verify this signature to ensure the request came from Fillout.
const crypto = require('crypto');
function verifyWebhookSignature(payload, signature, secret) {
const expectedSignature = crypto
.createHmac('sha256', secret)
.update(JSON.stringify(payload))
.digest('hex');
return crypto.timingSafeEqual(
Buffer.from(signature),
Buffer.from(expectedSignature)
);
}
// In your webhook handler
app.post('/webhooks/fillout', (req, res) => {
const signature = req.headers['x-webhook-signature'];
const isValid = verifyWebhookSignature(req.body, signature, YOUR_WEBHOOK_SECRET);
if (!isValid) {
return res.status(401).send('Invalid signature');
}
// Process the webhook...
res.status(200).send('OK');
});
Each webhook request includes these headers:
| Header | Description |
|---|
Content-Type | application/json |
X-Webhook-Signature | HMAC-SHA256 signature of the payload |
X-Webhook-Event | The event type (e.g., record.created) |
X-Webhook-ID | The unique event ID |
User-Agent | Fillout-Webhooks/1.0 |
Retry Policy
If your webhook endpoint returns a non-2xx status code or times out, we’ll retry the delivery:
- Maximum retries: 5
- Retry delays: 5s, 10s, 20s, 40s, 80s (exponential backoff)
- Timeout: 30 seconds per request
Your endpoint should return a 2xx status code within 30 seconds to acknowledge receipt. Process the webhook asynchronously if needed.
Limits
| Limit | Value |
|---|
| Max webhooks per database | 100 |
| Max events per webhook | 20 |
| Max URL length | 2,048 characters |
| Max payload size | 256 KB |
| Delivery timeout | 30 seconds |
Best Practices
- Respond quickly: Return a 2xx status immediately, then process asynchronously
- Handle duplicates: Use the event
id for idempotency in case of retries
- Verify signatures: Always verify the
X-Webhook-Signature header
- Use HTTPS: Webhook URLs must use HTTPS for security
- Store your secret: Save the webhook secret securely - it’s only shown once on creation