Airtable to Attio Sync
Keep Airtable as the working data surface and Attio as the CRM without manual ETL. This custom-build pattern surfaces matched Airtable contacts and records in Attio through an airtable_contact mapping object, so client-facing teams can see Airtable row context without copying fields by hand.
Who this is for
This build is for teams that operate working lists, inventories, or contact datasets in Airtable but manage customer relationships in Attio. It fits when Airtable has the row-level context and Attio is where sales, account, or client-service teams need to act on it.
It is also a fit when the team wants contact context from Airtable rows surfaced in Attio without manual copy-paste. During scope we decide which rows, fields, bases, and email rules belong in the CRM view.
How the sync works
The sync has three parts: Airtable change detection, Attio writes through a mapping object, and soft-delete handling for destroyed Airtable records.
Change detection
Airtable webhooks are notification pings. When Airtable calls the notification URL, the app verifies the request, then calls GET /v0/bases/{baseId}/webhooks/{webhookId}/payloads with the stored cursor to pull detailed change payloads.
The app persists the returned cursor and continues while mightHaveMore is true. This means webhook delivery and payload processing can be retried without guessing which payload came next.
We use two webhooks as a deliberate design pattern: one narrow add/update webhook scoped with watchDataInFieldIds for mapped fields, and one delete-focused webhook for remove change types. Airtable field filters are about cell-value changes, while deletion payloads report destroyed record IDs, so splitting the subscriptions keeps deletion handling explicit.
Attio write shape
Airtable rows write into an airtable_contact mapping object in Attio. The mapping object stores airtable_record_id as the unique source key, base_id for base-level scope, a person 1:1 record reference for the primary matched Person, associated_people as a many-to-many Person reference, and the mapped Airtable column attributes agreed during scope.
The airtable_contact object and the JSON field-mapping config are implementation choices built on Attio's custom object, custom attribute, and record-reference capabilities. Airtable supplies the row data and change payloads. Attio supplies the object model used to hold CRM-facing context.
Soft-delete handling
Airtable webhook payloads can report destroyed record IDs. This integration writes a deleted_at timestamp on the matching Attio mapping record instead of hard deleting it, so historical CRM context stays available while active views can filter deleted Airtable rows out.
What we configure during scope
The scoping call turns the Airtable-to-Attio pattern into your build. These decisions are made before the app is installed.
| Decision | What we decide with you |
|---|---|
| Airtable bases and tables | Which bases and tables are allowed to feed Attio, and which tables stay out of the CRM surface. |
| Field map | Which Airtable fields map into Attio attributes through the JSON config, using stable field IDs where rename risk matters. |
| Primary and secondary email | Which fields identify the primary Person, whether a secondary contact should be linked, and how unmatched emails should be handled. |
| Per-base coverage | Whether each base needs the same mapping, its own field map, its own backfill rule, or separate webhook coverage. |
| Sync-status write-back | Whether Airtable should receive a status or timestamp after Attio writes. This decision controls whether data.records:write is added to the PAT scope. |
| Soft-delete behavior | Whether destroyed Airtable records should mark deleted_at, update a status field, or trigger a workspace-specific review flow. |
| Backfill approach | How much historical Airtable data should be imported, which filters apply, and how the queue should respect Airtable rate limits. |
Why this works with the Airtable API
Airtable exposes the webhook pieces this pattern needs. The Webhooks overview describes the notification-then-pull model. The integration creates base webhooks with Create a webhook, then pulls detailed changes with List webhook payloads.
Initial backfills and reconciliation use List records. Airtable returns pages of up to 100 records, uses offset for pagination, supports repeated fields[] query parameters for field selection, and supports filterByFormula when a backfill needs Airtable formula semantics.
Authentication uses Airtable personal access tokens scoped to the resources in the build. Airtable documents the available scopes and the support flow for creating PATs. The standard setup uses data.records:read, schema.bases:read, and webhook:manage; data.records:write is added only if Airtable write-back is in scope.
Rate limits and backfill pacing
Airtable's Web API limit is 5 requests per second per base across tiers. Airtable also documents a 50 requests per second limit for all traffic using personal access tokens from one user or service account, and a 429 response requires waiting 30 seconds before subsequent requests will succeed (Managing API Call Limits). Backfills are queued and paginated so live sync and setup jobs do not fight the same base limit.
How reliability is handled
Airtable webhook notifications are verified before processing. Airtable sends X-Airtable-Content-MAC, and verification uses HMAC-SHA256 over the raw request body with the base64-decoded macSecretBase64 returned when the webhook is created.
Idempotency is based on webhook ID, the persisted payload cursor, and each payload's baseTransactionNumber. The app processes the payload batch, writes the mapped Attio records, then persists the returned cursor so retries resume from stored payload state.
Airtable webhooks created with modern API auth expire after 7 days unless extended. The app keeps active webhooks alive through the Refresh a webhook endpoint and through payload-list calls, since Airtable documents that listing payloads can also extend an active expiring webhook.
How setup works
Setup starts with PAT scoping. The Airtable token should be limited to the bases involved and should use the minimum scopes for the job: data.records:read, schema.bases:read, and webhook:manage. Add data.records:write only if the build writes sync status or another approved value back to Airtable.
- Scope the Airtable and Attio model. We confirm which Airtable bases and tables feed Attio, which Airtable fields should map to the airtable_contact object, how People matching should work, whether Airtable write-back is needed, and how much historical data should be backfilled.
- Generate a scoped Airtable PAT. An approved Airtable admin or service account creates a personal access token scoped to the bases involved. The token needs data.records:read, schema.bases:read, and webhook:manage. Add data.records:write only when Airtable sync-status write-back is part of scope.
- Install the Attio app and paste the config. We install the custom Attio app into your workspace. The setup surface holds the Airtable PAT and the JSON config that names each base, table, field map, and email-handling rule.
- Provision webhooks and the mapping object. The app provisions the airtable_contact mapping object, creates the agreed Airtable webhooks, stores the webhook MAC secrets and payload cursors, and verifies that each base and table can be read.
- Start live sync and backfill when scoped. Live Airtable notifications start the notification-then-pull path. If historical rows are in scope, the app backfills Airtable records through the list-records API before relying on live webhook changes for ongoing updates.
Known limitations
One-way by default
The standard build syncs Airtable to Attio. Airtable write-back is limited to scoped fields such as sync status, and automatic Attio-to-Airtable updates are a separate scope item.
Computed and linked fields sync as strings
Linked records, lookups, rollups, and formulas are treated as the displayed Airtable value unless a separate relationship model is scoped.
Attachments are not synced by default
Binary attachments stay in Airtable. If a workflow needs visibility in Attio, we can scope a text URL workaround rather than copying files.
Configuration is JSON-only
The field map is a JSON config surface, not a self-serve visual mapper. We set it during implementation and treat changes as controlled ops work.
Multiple tables in one base need multiple entries
Each table needs an explicit config entry so field maps, webhook filters, cursors, and backfill rules stay clear.
Backfills are rate-paced
Large bases take time to import because the app respects Airtable's per-base and per-PAT rate limits, including the 30-second wait after 429.
Custom install, not a Marketplace app
This is a scoped custom engagement. It is not a public Airtable Marketplace app, not an Attio Marketplace app, and not a one-click install.
Schema changes need controlled reprovisioning
New Airtable fields, renamed fields, changed Attio attributes, or changed email rules need a config update and reprovisioning pass before relying on new webhook writes.
Frequently asked questions
Is this an Airtable Marketplace app?
No. This is a client-proven Airtable-to-Attio custom app pattern, not a public Airtable or Attio Marketplace app. We adapt the same scaffolding to the bases, tables, field map, People matching rules, and support controls each workspace needs.
Which direction does the sync run?
The default direction is Airtable to Attio. Airtable remains the working data surface, and Attio receives the CRM context. The only Airtable write-back in the standard pattern is an optional sync-status field, and that is included only when it is scoped.
How are Airtable rows matched to Attio People?
Rows are matched through configured email fields. The primary email can link to the person reference on the airtable_contact record, while a secondary email can link additional People through associated_people. The mapping object itself is keyed by airtable_record_id and base_id so repeated deliveries update the same Attio record.
What happens when a row is deleted in Airtable?
Airtable webhook payloads can report destroyed record IDs. This integration records that event by setting deleted_at on the matching airtable_contact record in Attio instead of hard deleting it, so CRM history remains available.
How does the integration avoid duplicate writes?
Airtable notifications are treated as pings. The app pulls payloads with the stored webhook cursor, processes payloads by webhook ID and baseTransactionNumber, then persists the returned cursor after successful handling. Retried deliveries update the existing Attio records rather than creating new ones.
Do Airtable rate limits affect backfills?
Yes. Airtable documents 5 requests per second per base across tiers and 50 requests per second for PAT traffic from one user or service account. Backfills are paced, paginated, and retried after the required 30-second wait when Airtable returns 429.
Can one setup cover multiple Airtable bases?
Yes. The JSON config can include multiple base entries. Each entry defines its own base, table, field map, webhook coverage, and cursor state, while the Attio mapping object keeps base_id and airtable_record_id available for matching.
What do we need from Airtable before setup?
You need an Airtable account with access to the bases in scope, the base and table IDs, the fields that should sync, and a personal access token scoped to those bases with data.records:read, schema.bases:read, and webhook:manage. Add data.records:write only if write-back is included.
API sources checked
- Airtable Webhooks overview
- Airtable Create a webhook
- Airtable List webhook payloads
- Airtable Refresh a webhook
- Airtable List records
- Airtable Scopes
- Airtable Getting started with the Web API
- Airtable Managing API Call Limits
- Airtable Authentication
- Airtable Creating personal access tokens
- Attio People standard objects
- Attio Record reference
- Attio Create an attribute
- Attio Upsert a Record
- Attio App SDK record actions
- Attio App SDK bulk record actions