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 object | IngestItem type | Stable id | Notes |
|---|---|---|---|
| Product | product | product:<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 page | page | page:<id> | Resolved /pages/<handle> storefront URL. |
| Blog article | post | article:<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
npm install
npm run buildConfigure
All configuration is via environment variables. In a real public app these come from the OAuth install plus the bAInquet token-mint step.
| Variable | Purpose |
|---|---|
BAINQUET_API_URL | bAInquet ingest base (default https://api.bainquet.online/v1) |
BAINQUET_CONNECTOR_TOKEN | The connectorId.secret from the dashboard |
BAINQUET_WEBSITE_ID | The website the token is scoped to |
BAINQUET_SITE_DOMAIN | Storefront host, e.g. acme.com (must equal each item URL host) |
SHOPIFY_WEBHOOK_SECRET | Shopify webhook signing secret (inbound HMAC) |
SHOPIFY_ADMIN_TOKEN | Shopify Admin API access token (read-only) for backfill |
SHOPIFY_SHOP_DOMAIN | acme.myshopify.com |
SHOPIFY_STOREFRONT_ORIGIN | https://acme.com |
SHOPIFY_CURRENCY | ISO 4217, default USD |
SHOPIFY_DEFAULT_LANGUAGE | BCP-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
idleheartbeat for a clean disconnect.
node dist/scripts/webhook-server.js # listens on :8472 /webhooks/shopifyHow backfill works
After install, or to reconcile missed webhooks, page the whole catalog and content and batch-ingest:
npm run backfillIt 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.