Skip to content

Ghost connector

A standalone Node connector that maps a Ghost publication's posts and pages to bAInquet IngestItems through the Ghost Content API (backfill) and Ghost webhooks (incremental), signs every request with the connector HMAC scheme, and POSTs idempotent items and batches.

The connector lives under connectors/ghost/bainquet-connector and sends X-Connector-Type: ghost. It ships outside the bAInquet monorepo, 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

Ghost objectIngestItem typeStable idNotes
Postpostpost:<id>Rendered html plus plaintext, tags, authors, SEO meta, feature image.
Pagepagepage:<id>Same field set as posts.
  • URL. Ghost's own absolute URL (its host equals X-Site-Domain), falling back to <siteOrigin>/<slug>/.
  • SEO. meta_title, meta_description, and og_* map into json.seo; a differing canonical becomes the item canonical_url.
  • Exclusions. Draft and scheduled posts, and members-only or paid content (visibility != "public"), are excluded to avoid leaking gated content. Internal tags (#-prefixed or visibility:"internal") are filtered out.

Install

bash
npm install
npm run build

Configure

All configuration is via environment variables.

VariablePurpose
BAINQUET_API_URLbAInquet ingest base (default https://api.bainquet.online/v1)
BAINQUET_CONNECTOR_TOKENThe connectorId.secret
BAINQUET_WEBSITE_IDThe scoped website id
BAINQUET_SITE_DOMAINSite host, e.g. acme.com
GHOST_API_URLGhost site URL, e.g. https://acme.com
GHOST_CONTENT_API_KEYGhost Content API key (read-only) for backfill
GHOST_SITE_ORIGINPublic origin (default = GHOST_API_URL)
GHOST_DEFAULT_LANGUAGEDefault en
GHOST_WEBHOOK_SECRETOptional URL-path secret (see below)

How incremental sync works

Ghost webhooks are not cryptographically signed

Unlike Shopify, Wix, or Webflow, Ghost sends no HMAC header on its webhooks, so there is no inbound signature to verify. The realistic guard is a hard-to-guess secret embedded in the webhook URL path (GHOST_WEBHOOK_SECRET) over an HTTPS-only deployment, treating the URL itself as the shared secret.

Ghost also does not send the event name in a header; the event is implied by which webhook URL you configured. Configure each Ghost webhook to its matching path:

POST /webhooks/ghost/<secret>/post.published
POST /webhooks/ghost/<secret>/post.published.edited
POST /webhooks/ghost/<secret>/post.unpublished
POST /webhooks/ghost/<secret>/post.deleted
POST /webhooks/ghost/<secret>/page.published   (and .edited / .unpublished / .deleted)
bash
node dist/scripts/webhook-server.js   # :8475
  • published and published.edited map the object and POST to POST /v1/ingest/item.
  • unpublished and deleted POST to POST /v1/ingest/delete (tombstone by stable id).

The outbound bAInquet request is still HMAC-signed: that is the connector token's job, separate from the unsigned inbound Ghost webhook.

How backfill works

bash
npm run backfill

It pages the Ghost Content API (posts and pages, with tags and authors, formats=html,plaintext), maps published public objects, and batch-POSTs in chunks of 500. It is idempotent: unchanged items return skipped.

Out of scope

This connector is a content mapper, signer, webhook glue, and backfill. A hosted connection UI, a durable outbox, and llms.txt or ai.json emission would wrap this core and are not included. No LLM is used anywhere.

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 include X-Connector-Type: ghost, alongside Authorization: Bearer <token>, X-Site-Domain, 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 against the known-good vector, plus mapper and webhook unit tests. The full scheme is documented in Ingestion and signing.

Owner-controlled structured data for AI.