What it is
A small Node.js service that listens for Shopify fulfillment webhooks and mirrors “fulfilled” orders into Fishbowl Advanced. It’s designed to be reliable under real webhook conditions (duplicates, retries, partial failures) and easy to validate locally.
Outcomes
- Built a fully runnable local demo (no real Shopify store or Fishbowl instance required) to prove the end-to-end flow.
- Implemented idempotent processing using
X-Shopify-Event-Idpersisted in SQLite, preventing double-imports on retry storms. - Added a “trust but verify” fulfillment gate: webhook receipt isn’t enough — the bridge confirms the order is actually FULFILLED before triggering Fishbowl.
Problem
Shopify webhooks are at-least-once: duplicates are normal, not exceptional. Without verification + idempotency, a fulfillment webhook can easily become two Fishbowl imports… and now you’re reconciling chaos in an inventory system that does not care about your feelings.
Also: it’s annoying to develop an integration when you don’t always have a real Shopify/Fishbowl environment available.
Constraints
- Webhooks can be duplicated and reordered; the service must be safe to run continuously.
- Fishbowl failures should be visible (logs/alerts), but the bridge should not “half apply” anything back to Shopify.
- Keep scope intentionally tight: no job queue framework, no dashboard, no multi-tenant SaaS machinery.
Solution
- Receive Shopify fulfillment webhook:
POST /webhooks/shopify - Verify authenticity via Shopify HMAC header (
X-Shopify-Hmac-Sha256) - De-dupe using
X-Shopify-Event-Idstored in SQLite (persisted across restarts) - Confirm the order is actually FULFILLED (Shopify GraphQL in real mode; mocked in demo mode)
- If fulfilled, trigger Fishbowl Advanced via Import endpoint (
POST /api/import/:name) using generated CSV payload - If Fishbowl fails, return HTTP 200 (ack the webhook), log the error, and optionally email an alert — no Shopify mutation
Architecture
- Bridge service: HTTP server exposing
/healthand/webhooks/shopify - Idempotency store: SQLite database of processed event IDs (
./data/idempotency.sqliteby default) - Shopify integration:
- real mode: GraphQL fulfillment verification
- mock mode: returns “FULFILLED” for demo orders
- Fishbowl integration:
- login → import → logout sequence
- CSV headers + row template configured via env vars
- Notification layer (optional): SMTP email alerting on import failures
Data flow (conceptually): Shopify → Bridge → (SQLite dedupe) → (GraphQL verify) → Fishbowl Import → (optional alert)
Notable technical details
- HMAC verification for webhook authenticity (
X-Shopify-Hmac-Sha256) - True idempotency using Shopify’s delivery event ID (
X-Shopify-Event-Id) persisted in SQLite - Safety-first side effects: Fishbowl errors don’t cause any attempt to “fix” Shopify; they’re surfaced via logs/alerts
- Configurable CSV mapping: headers + row template are env-driven with placeholder substitution:
{{orderNumber}},{{trackingNumber}},{{carrier}},{{shipDate}}
- Local integration harness:
- Mock Fishbowl server captures login/import/logout calls
- Demo runner executes 3 scenarios: success, duplicate webhook, forced Fishbowl failure