Brian · Portfolio
evergreenwebhooksidempotencyshopifyfishbowl

A tiny webhook bridge pattern: verify, dedupe, then act

A practical checklist for turning noisy at-least-once webhooks into safe, idempotent integrations (Shopify → Fishbowl example).

Related project: Shopify ↔ Fishbowl Fulfillment Bridge
Repo: bju12290/shopify-fishbowl-fulfillment-bridge

The point

Webhooks are at-least-once. That means duplicates are not a bug — they’re an expected feature of reality. So if a webhook triggers something expensive (inventory, fulfillment, accounting), you need a tiny safety harness:

Verify → Dedupe → Confirm state → Act → Alert (don’t thrash).

That pattern is the difference between “works in a demo” and “doesn’t randomly create duplicate imports at 3AM.”

The practical bit

Here’s the minimal checklist I follow for webhook → external-system bridges:

1) Verify the sender (authenticity)

Treat inbound webhooks like stranger-danger mail. For Shopify, that’s HMAC verification with the webhook secret.

If you skip this, you’re basically offering a public endpoint that can be used to trigger your downstream system.

2) Dedupe by an event ID (idempotency)

Most webhook providers include a delivery/event ID. Store it somewhere persistent (SQLite is plenty for a “small bridge”), then:

  • if you’ve seen this event ID before: acknowledge and skip
  • if you haven’t: record it, then proceed

This is the core move that makes at-least-once delivery safe.

3) Confirm the world is actually in the expected state

A webhook is a notification, not the source of truth. Before you kick off side effects, double-check that the thing is really true.

In this project, the webhook says “fulfilled,” but the bridge still confirms the order is FULFILLED (via Shopify GraphQL in real mode; mocked in demo mode).

4) Do the side effect (keep it narrow)

Then do one job: generate CSV from a template, trigger Fishbowl Advanced Import, and get out.

A bridge is not a platform. It’s a cable.

5) Fail loudly, but don’t amplify chaos

If the downstream system fails:

  • log the error
  • optionally notify (email/alerts)
  • and be careful about returning non-2xx to the webhook sender (retries can become a storm)

The goal is: surface failures without creating duplicates.

Examples

A mental model that helps:

Incoming webhooks are “noisy.”
Your bridge’s job is to output one clean action per real-world event.

Data flow: Shopify webhook → verify signature → dedupe event ID (SQLite) → confirm fulfillment state → generate CSV → Fishbowl Import → alert on failure

If you want to see this pattern exercised end-to-end, that’s exactly what the Shopify ↔ Fishbowl Fulfillment Bridge demo does: success path, duplicate webhook path, and forced failure path.

Closing

This isn’t glamorous engineering. It’s the kind that prevents “why are there two imports for order 1001?” conversations.

Whenever a webhook triggers money-moving or inventory-moving actions, this tiny pattern pays rent: verify → dedupe → confirm → act → alert.