Skip to main content

Inbound safety and the field whitelist

Inbound sync lets edits made in Airtable flow back into your SKU.io catalog. Because a spreadsheet edit could otherwise touch operational data, inbound is deliberately narrow: it can write only a short, safe list of descriptive fields, and it never touches inventory, pricing, costs, dates, or order status. This page explains the two safety mechanisms behind that guarantee — the per-dataset field whitelist and the webhook lifecycle that keeps inbound flowing without ever getting stuck.

For the outbound side and the overall record lifecycle, see How Airtable sync works. To act on the changes inbound proposes, see Review changes from Airtable.

Only three datasets accept inbound

Of the seven syncable datasets, only Products, Sales Orders, and Suppliers can receive inbound writes. Inventory Levels, Sales Order Lines, Purchase Orders, and PO Lines are outbound-only — SKU.io pushes them to Airtable but never reads changes back. A mapping on an outbound-only dataset has no inbound path to scan.

Each inbound-capable dataset has its own applier that owns a hard-coded whitelist of writable fields. Anything a mapping tries to send that is not on that dataset's whitelist is silently dropped while the diff is built — it never reaches your data. This is the security gate: the whitelist lives in code, not in configuration, so no mapping setting can widen it.

The safe field whitelist, per dataset

The tables below are the exact, complete set of SKU.io fields each dataset's applier will write. For the full picture of which Airtable columns map to which SKU.io sources, see the Dataset field reference.

Products

Products are the richest inbound dataset. Scalar columns are written verbatim; Brand, Tags, and Category are resolved through dedicated relation handlers rather than raw column writes.

Writable fieldKindNotes
NameScalarWritten verbatim
BarcodeScalarWritten verbatim
MPNScalarWritten verbatim
HTS codeScalarWritten verbatim
Weight, Length, Width, HeightScalarDimensions, written verbatim
Unit costScalarThe only writable cost field
BrandRelationMatched by name, and created if it does not exist
TagsRelationParsed from a list or comma-separated text and synced
CategoryRelationAttached only by an exact existing name — never created

What Products inbound will never write: inventory levels, product price, average_cost, or any cost beyond the manual Unit cost. Those sources are ignored even if a mapping points an Airtable column at them.

The Category and Brand handlers behave differently on purpose. Categories form a path-based tree, so inbound attaches a category only when the name exactly matches an existing one; an unknown category name is skipped without error rather than inventing a new tree node. A Brand, by contrast, is created from its name if none matches, because brands are flat.

Sales Orders

Only descriptive customer metadata is writable. Everything that drives the fulfillment and financial state machine is excluded.

Writable field
Customer name
Customer email
Customer company
Customer phone
Customer PO number
Memo for customer

Order status, fulfillment status, payment status, order dates, and tracking are never writable — a spreadsheet edit must not move an order through its lifecycle. Sales Orders also never auto-create: an Airtable row whose order number has no SKU.io match is queued as unmatched, never conjured into a new order.

Suppliers

Suppliers accept contact and company descriptors only.

Writable field
Name
Company name
Primary contact name
Email
Messaging phone
Website

The operational supplier configuration — lead times, payment terms, deposits, PO automation, and default warehouses — is never mutable from Airtable. Like Sales Orders, Suppliers do not auto-create in this version; unmatched rows are queued rather than turned into new supplier records.

Auto-create is Products-only and opt-in

When an Airtable row's key has no matching SKU.io record, inbound normally queues it as an unmatched change for you to resolve. A row can instead be created automatically, but only when two conditions both hold: the dataset supports auto-create (only Products does), and the mapping has Create missing products turned on. Even then, the new product is built from the same scalar whitelist and relation handlers — auto-create widens nothing.

Sales Orders and Suppliers can never auto-create, so their unmatched rows always wait in the review queue regardless of any mapping setting.

How the whitelist is enforced

For each Airtable record, SKU.io walks the mapping's field map and, for every entry, asks the dataset's applier whether the source is whitelisted. If it is not, the entry is skipped before any comparison happens — so a forbidden source produces no diff and can never be applied, whether the mapping is in Review before applying or Apply automatically mode. Only whitelisted sources whose Airtable value actually differs from the current SKU.io value become part of the proposed change.

Because the gate runs while the diff is built, turning on Apply automatically does not bypass it. Auto-apply changes only how an already-safe diff is applied (immediately versus after review), never what is eligible.

The inbound webhook lifecycle

Inbound is near-real-time. When you save an inbound mapping, SKU.io registers an Airtable webhook on the mapped table so that edits in Airtable ping SKU.io within seconds. Registration is best-effort: if it fails, mapping creation still succeeds and the scheduled fallback scan keeps inbound working.

Pings are untrusted signals, not data

An Airtable ping carries no trusted payload. SKU.io treats the ping body as untrusted — it only triggers an authenticated fetch, in which SKU.io calls Airtable back with its own credentials to pull the list of changed record ids since the mapping's stored cursor. The record data always comes from that authenticated fetch, never from the ping itself. Tenancy and the target connection are resolved from an opaque token embedded in the receiver URL.

Cursors advance only after success

The cursor that marks "everything up to here has been processed" is committed only after the changed records are successfully processed. If processing fails partway through, the cursor stays put and the same changes are re-polled on the next run. Inbound is therefore at-least-once — a failure re-processes rather than loses changes.

When a poll succeeds and reports no changes, SKU.io advances the cursor and does no table read at all — the common quiet case costs nothing.

Webhooks lapse, and inbound heals itself

Airtable's OAuth-created webhooks expire roughly every 7 days. SKU.io handles a lapsed webhook automatically so inbound never sticks:

  • Stored expiry is in the past. Before polling, SKU.io checks whether the webhook's stored expiry has passed. If it has, SKU.io raises an Airtable Webhook Lapsed alert, forgets the dead webhook, registers a fresh one, and full-scans the whole table that run so nothing is missed.
  • A poll fails because the webhook is gone. If the webhook was deleted or its cursor is stale, the poll throws. SKU.io logs a warning, raises the same lapsed alert, drops and re-registers the webhook, and falls through to a full-table scan so the run still makes progress.
  • A poll succeeds. SKU.io resolves any lingering Airtable Webhook Lapsed alert — the condition auto-clears the moment inbound is healthy again.

The Airtable Webhook Lapsed alert is deduped per mapping, so a persistent problem stays a single occurrence-counted alert rather than a flood, and it auto-resolves once the webhook is re-registered. You can watch for it on the Activity tab; see Monitor sync health.

Two safety nets keep inbound fed

Beyond self-healing on failure, two scheduled jobs make sure inbound never depends on a single webhook staying alive:

  • A daily refresh sweep re-arms every inbound webhook expiring within the next couple of days, extending it in place before it can lapse — keeping near-real-time inbound alive without a gap.
  • The fallback full-table scan runs on a fixed every-3-hours schedule, independent of the mapping's Schedule setting (which controls outbound cadence only). Even a Manual only inbound mapping is swept every three hours, and even if every webhook were down, this scan would still feed the review queue.

Together, the webhook provides speed, and the every-3-hours scan provides the guarantee: inbound changes are eventually picked up even when real-time delivery falters.

Last verified: