Airtable Integration
Receive and route Airtable webhooks for record, field, and table changes across your bases.
Setup
1. Create a Source in Hookbase
curl -X POST https://api.hookbase.app/api/sources \
-H "Authorization: Bearer whr_your_api_key" \
-H "Content-Type: application/json" \
-d '{
"name": "Airtable Production",
"slug": "airtable",
"provider": "airtable",
"signingSecret": "your-mac-secret-base64"
}'Save your webhook URL:
https://api.hookbase.app/ingest/{orgSlug}/airtableWarning
Paste macSecretBase64 exactly as Airtable returns it. Airtable hands you the MAC secret as a
base64 string, and its own example decodes that string to raw bytes before hashing. Hookbase does
the same: the signingSecret you store is base64-decoded and the decoded bytes are the HMAC key.
Storing the decoded value, or re-encoding it, produces a well-formed digest that never matches, and
every event arrives with signature_valid: false.
2. Create the Airtable Webhook
Airtable webhooks are created through its API, not through the Airtable UI.
POST https://api.airtable.com/v0/bases/{baseId}/webhooks- Set
notificationUrlto your Hookbase ingest URL - Describe the changes you want in the
specificationobject - Read
macSecretBase64out of the response and store it on your Hookbase source assigningSecret - Make sure the token creating the webhook has access to the base you are watching
See Airtable's webhooks overview for the
full specification shape.
Info
Airtable notifications are cursor-based pings, not full payloads. The body Airtable POSTs tells
you that something changed in a base, along with the webhook and base ids. To read what changed,
call Airtable's listPayloads endpoint with the cursor.
That shape is worth planning for: a Hookbase route delivers the ping to your destination, and your destination handler is what fetches the payloads. Filters and transforms on this source can only see the ping.
3. Create Destinations and Routes
# Create a destination
curl -X POST https://api.hookbase.app/api/destinations \
-H "Authorization: Bearer whr_your_api_key" \
-H "Content-Type: application/json" \
-d '{"name": "Airtable Sync", "slug": "airtable-sync", "url": "https://api.myapp.com/webhooks/airtable"}'
# Create a route
curl -X POST https://api.hookbase.app/api/routes \
-H "Authorization: Bearer whr_your_api_key" \
-H "Content-Type: application/json" \
-d '{"name": "Airtable to Sync Service", "sourceId": "src_...", "destinationId": "dst_..."}'Signature Verification
Airtable signs webhooks with HMAC-SHA256 over the raw request body. The hex digest is sent in the
X-Airtable-Content-MAC header behind an hmac-sha256= prefix:
X-Airtable-Content-MAC: hmac-sha256=8f1c0a2b3d4e5f60718293a4b5c6d7e8f9012a3b4c5d6e7f8091a2b3c4d5e6f7Hookbase verifies this automatically once the source has both fields set:
{
"provider": "airtable",
"signingSecret": "your-mac-secret-base64"
}The key is the base64-decoded macSecretBase64, which Hookbase handles for you — see the warning
above. There is no timestamp in this scheme, so nothing expires and nothing needs a clock tolerance.
Once you have confirmed events are arriving with signature_valid: true, set
rejectInvalidSignatures: true on the source to have unverified requests refused with 401 instead
of stored.
Common Events
| Event | Description |
|---|---|
record.created | A record was created |
record.updated | A record was updated |
record.deleted | A record was deleted |
field.created | A field was created |
field.updated | A field was updated |
table.created | A table was created |
Record Created
{
"base": {
"id": "appABC123"
},
"webhook": {
"id": "ach_DEF456"
},
"timestamp": "2024-01-15T10:30:00.000Z",
"actionMetadata": {
"source": "client",
"sourceMetadata": {
"user": {
"id": "usr_GHI789",
"email": "dev@example.com",
"name": "Developer"
}
}
},
"payloads": [
{
"tableId": "tbl_JKL012",
"changedRecordsById": {
"rec_MNO345": {
"current": {
"cellValuesByFieldId": {
"fld_PQR678": "New task",
"fld_STU901": "To Do"
}
}
}
},
"createdRecordsById": {
"rec_MNO345": {
"cellValuesByFieldId": {
"fld_PQR678": "New task",
"fld_STU901": "To Do"
}
}
}
}
]
}Transform Examples
A javascript transform receives the parsed payload and returns the body Hookbase delivers.
Flatten the Ping for a Sync Worker
function transform(payload) {
return {
base_id: payload.base.id,
webhook_id: payload.webhook.id,
changed_at: payload.timestamp,
changed_by: payload.actionMetadata &&
payload.actionMetadata.sourceMetadata &&
payload.actionMetadata.sourceMetadata.user
? payload.actionMetadata.sourceMetadata.user.email
: null,
change_source: payload.actionMetadata ? payload.actionMetadata.source : null,
table_ids: (payload.payloads || []).map(p => p.tableId)
};
}Slack Notification
function transform(payload) {
const tables = (payload.payloads || []).map(p => p.tableId).join(", ");
return {
text: `Airtable base ${payload.base.id} changed`,
blocks: [
{
type: "section",
fields: [
{ type: "mrkdwn", text: `*Base:*\n${payload.base.id}` },
{ type: "mrkdwn", text: `*Tables:*\n${tables || "unknown"}` }
]
}
]
};
}Filter Examples
Filter conditions read dotted paths out of the payload. logic must be AND or OR.
One Table Only
{
"name": "Tasks Table Only",
"logic": "AND",
"conditions": [
{
"field": "payloads.0.tableId",
"operator": "equals",
"value": "tbl_JKL012"
}
]
}Ignore Changes Made by Automations
{
"name": "Human Edits Only",
"logic": "AND",
"conditions": [
{
"field": "actionMetadata.source",
"operator": "equals",
"value": "client"
}
]
}Headers
| Header | Description |
|---|---|
X-Airtable-Content-MAC | Hex HMAC-SHA256 of the raw body, prefixed hmac-sha256= |
Hookbase does not forward these headers to your destination. A delivery is a fresh request:
Hookbase sets Content-Type, User-Agent: Hookbase/1.0, X-Delivery-ID and X-Event-ID, then
adds the headers and auth you configured on the destination. Nothing Airtable sent reaches your
handler as a header — read what you need out of the body, or set it on the destination yourself.
Of the incoming headers, only content-type, user-agent, x-github-event, x-gitlab-event and
stripe-signature are stored on the event, so those are the only ones visible later in the
dashboard or the API.
Troubleshooting
Signature Verification Failed
- Confirm the source's
providerisairtable— a source left oncustomreadsX-Signature,X-Webhook-SignatureandX-Hub-Signature-256, none of which Airtable sends - Confirm
signingSecretis themacSecretBase64string from the webhook-creation response, stored verbatim and not decoded first - Confirm the secret belongs to the same webhook that is delivering — each Airtable webhook has its own MAC secret
No Events Arriving
- Airtable webhooks expire. Refresh the webhook before its expiry, or recreate it
- Check
notificationUrlmatches your ingest URL exactly, including the org and source slugs - Verify the
specificationactually covers the changes you are making
Duplicate Events
Hookbase has no provider event-id extractor for Airtable, so the default auto dedup strategy falls
back to hashing the payload. Two genuinely identical pings inside the dedup window collapse into one;
two pings with different cursors do not. Set dedupCustomHeader only if you have a proxy adding an
idempotency header of your own.