Webflow connector
A standalone Node connector that streams a Webflow site's CMS content to the bAInquet ingestion API, mapping each CMS collection item by its field schema to the canonical IngestItem shape, signing every request with the bq.connector.hmac.v1 HMAC scheme, and POSTing idempotent items and batches.
Stable HMAC parity-tested: it vendors the SDK signer (src/bainquet-sign.ts, a self-contained copy proven byte-for-byte against the project-wide known-good vector).
What it maps
Each CMS item is mapped using its collection schema (field types from the Webflow Data API v2):
| Webflow field type | Mapping |
|---|---|
| Rich text | item html (plus derived text); also json.fields (plain) |
| Plain text / option | json.fields[<slug>] |
| Reference / multi-reference | json.relations[<slug>]: related item ids, never stringified into prose |
| Image | json.media[] (url plus alt) |
| Number | json.fields[<slug>]; typed price {value,unit,currency} when the collection's priceField is configured |
Collection to SourceType is configurable per collection slug (WEBFLOW_COLLECTION_MAP), with a deterministic heuristic default: blog, post, and news collections map to post (not article, which is not in the enum); product and shop collections map to product; everything else maps to collection.
- Stable id:
<collectionSlug>#<itemId>. url:<siteOrigin>/<collectionSlug>/<itemSlug>(host equalsX-Site-Domain).- Draft, archived, and never-published items are excluded.
References become structured relations, never prose, which keeps the relationship graph machine-readable downstream.
Install and build
npm install
npm run buildRequires Node 20.11 or newer.
Configuration
| Variable | Purpose |
|---|---|
BAINQUET_API_URL | default https://api.bainquet.online/v1 |
BAINQUET_CONNECTOR_TOKEN | connectorId.secret |
BAINQUET_WEBSITE_ID | scoped website id |
BAINQUET_SITE_DOMAIN | site host, for example acme.com |
WEBFLOW_WEBHOOK_SECRET | Webflow app client secret, for inbound webhook signature |
WEBFLOW_API_TOKEN | Webflow Data API token (read-only) |
WEBFLOW_SITE_ID | the Webflow site id |
WEBFLOW_SITE_ORIGIN | https://acme.com |
WEBFLOW_DEFAULT_LANGUAGE | default en |
WEBFLOW_COLLECTION_MAP | optional JSON: { "<slug>": { "sourceType": "...", "bodyField": "...", "priceField": "...", "currency": "..." } } |
Two separate secrets
WEBFLOW_WEBHOOK_SECRETverifies inbound Webflow webhooks:x-webflow-signature = hex HMAC-SHA256("<timestamp>:<rawBody>"), with a plus-or-minus 300s timestamp window (replay guard).BAINQUET_CONNECTOR_TOKENsigns the outbound HMAC to bAInquet.
WARNING
The two secrets sign in opposite directions. The Webflow secret verifies what Webflow sends you; the connector token authenticates what you send to bAInquet.
Mapping pattern
Mapping is field-type driven and implemented in mapper.ts. For each item the connector fetches the collection schema (cached), then maps each field by its type per the table above. Override the SourceType, body field, price field, or currency per collection slug with WEBFLOW_COLLECTION_MAP.
Incremental sync
Subscribe to collection_item_created, collection_item_changed, collection_item_deleted, collection_item_unpublished, and site_publish, and point them at the bundled webhook server:
node dist/scripts/webhook-server.js # :8474 /webhooks/webflow- created or changed fetches the collection schema (cached), maps the item, and POSTs to
POST /v1/ingest/item. - deleted or unpublished POSTs a tombstone to
POST /v1/ingest/delete. site_publishcues a reconciliation backfill to catch any dropped webhooks.
The inbound signature is verified before any processing.
Backfill
npm run backfillLists the site's collections, loads each schema, pages all items (offset paging, honoring 429), maps published items, and batch-POSTs in chunks of 500. Idempotent.
Signing
signingKey = HKDF-SHA256(secret, salt = websiteId, info = "bq.connector.hmac.v1", 32 bytes)
canonical = METHOD\npath\nsha256(body)\ntimestamp\nnonce\nwebsiteId
X-Signature = hex HMAC-SHA256(signingKey, canonical)Headers include X-Connector-Type: webflow. Retries on 5xx / 429 / network errors reuse the same Idempotency-Key. See Ingestion and signing.
Out of scope
This package is the content mapper, signer, webhook glue, and backfill. The full Webflow App (OAuth install, hosted connection UI, encrypted secret store, Postgres outbox, polling fallback, llms.txt and ai.json emission) wraps this core and is out of scope. No LLM anywhere.
Verifying the signer
npm testRuns the vendored-signer parity check (known-good vector) plus mapper tests (field-type mapping, reference to relations, typed price, draft exclusion, webhook-signature verify and replay).