Skip to content

Shopify connector

A standalone Node connector that maps a Shopify store's products, pages, and blog articles to bAInquet IngestItems, signs every request with the connector HMAC scheme, and POSTs idempotent items and batches.

The connector lives under connectors/shopify/bainquet-connector and sends X-Connector-Type: shopify. It ships outside the bAInquet monorepo (as a small hosted webhook handler), so it vendors the signer: src/bainquet-sign.ts is a self-contained copy of the canonical signer with only a node:crypto dependency. It requires Node 20.11 or later.

What it maps

Shopify objectIngestItem typeStable idNotes
Productproductproduct:<id>Variants into json.product.variants[] with typed price { value, unit, currency }, SKU, inventory, options. The lowest variant price becomes json.product.price. Draft, archived, or unpublished products are tombstoned, not ingested.
Online-store pagepagepage:<id>Resolved /pages/<handle> storefront URL.
Blog articlepostarticle:<id>article is not a valid SourceType, so blog content maps to post. The URL is /blogs/<blog>/<handle>.

The item url is always the resolved storefront URL (its host equals X-Site-Domain), never an Admin GID. Prices are always typed objects, never concatenated strings.

Install

bash
npm install
npm run build

Configure

All configuration is via environment variables. In a real public app these come from the OAuth install plus the bAInquet token-mint step.

VariablePurpose
BAINQUET_API_URLbAInquet ingest base (default https://api.bainquet.online/v1)
BAINQUET_CONNECTOR_TOKENThe connectorId.secret from the dashboard
BAINQUET_WEBSITE_IDThe website the token is scoped to
BAINQUET_SITE_DOMAINStorefront host, e.g. acme.com (must equal each item URL host)
SHOPIFY_WEBHOOK_SECRETShopify webhook signing secret (inbound HMAC)
SHOPIFY_ADMIN_TOKENShopify Admin API access token (read-only) for backfill
SHOPIFY_SHOP_DOMAINacme.myshopify.com
SHOPIFY_STOREFRONT_ORIGINhttps://acme.com
SHOPIFY_CURRENCYISO 4217, default USD
SHOPIFY_DEFAULT_LANGUAGEBCP-47, default en

Two separate secrets, do not conflate them

SHOPIFY_WEBHOOK_SECRET authenticates inbound Shopify webhooks (the X-Shopify-Hmac-Sha256 base64 HMAC over the raw body). BAINQUET_CONNECTOR_TOKEN signs the outbound request to bAInquet (the X-Signature hex HMAC). These are different secrets with different algorithms.

How incremental sync works

Subscribe these Admin API webhook topics and point them at your deployment of webhook-server.ts:

products/create  products/update  products/delete
pages/create     pages/update     pages/delete
articles/create  articles/update  articles/delete
app/uninstalled
  • create and update map the object and POST to POST /v1/ingest/item.
  • delete (and any update that becomes draft or unpublished) POSTs to POST /v1/ingest/delete (tombstone by stable id).
  • app/uninstalled sends a final idle heartbeat for a clean disconnect.
bash
node dist/scripts/webhook-server.js   # listens on :8472 /webhooks/shopify

How backfill works

After install, or to reconcile missed webhooks, page the whole catalog and content and batch-ingest:

bash
npm run backfill

It cursor-paginates the Admin REST API (products, pages, articles), honors Shopify's 429 leaky-bucket Retry-After, maps each object, and POSTs in chunks of 500 to POST /v1/ingest/batch. It is idempotent: unchanged items return skipped.

Out of scope

This connector is a content mapper, signer, webhook glue, and backfill. The full Shopify app surface (OAuth install flow, embedded Polaris admin UI, encrypted offline-token storage, a BullMQ outbox, and Markets or metaobjects extraction) wraps this core and is not included. No LLM is used anywhere; the mapping is deterministic.

HMAC signing

Every outbound request is signed exactly as the server verifies it, using the shared bq.connector.hmac.v1 scheme:

signingKey  = HKDF-SHA256(secret, salt = websiteId, info = "bq.connector.hmac.v1", 32 bytes)
canonical   = METHOD \n path \n sha256(body) \n timestamp \n nonce \n websiteId
X-Signature = hex HMAC-SHA256(signingKey, canonical)

Headers: Authorization: Bearer <token>, X-Site-Domain, X-Connector-Type: shopify, X-Connector-Version, X-Signature, X-Timestamp, X-Nonce, X-Body-Sha256, and Idempotency-Key. Retries on 5xx, 429, and network errors reuse the same Idempotency-Key with a fresh nonce and timestamp.

The signing is parity-tested: npm test runs the vendored-signer parity test, which asserts the known-good vector byte-for-byte, plus the mapper unit tests. The full scheme is documented in Ingestion and signing.

Owner-controlled structured data for AI.