ProjectR Docs
Architecture

Webhook Pipeline

HMAC verification, ingestion, dispatch, retry, and dead-lettering.

Thirteen POST endpoints live under /webhooks/shopify/*: products create/update/delete, orders create/updated/cancelled, customers create/update, inventory update, product listings add/remove, collections create/update.

Rules that keep it safe

  • One dispatcher. dispatchShopifyWebhook is the single topic→mutation switch, shared byte-for-byte by the live path and the retry path. Past drift between two parallel switches corrupted product data and stored customer IDs in different formats on each path.
  • Fail loudly. Success → 200. Internal failure → 500, so Shopify retries. A 2xx on failure loses the event.
  • Payloads live in their own table. webhookEvents holds a payloadId; webhookPayloads holds the JSON (16 MB query-limit lesson).
  • Pre-filter unknown products. Product/inventory events for products outside the mirror are acknowledged and dropped (the nightly sync would catch them anyway). product_listings/add is never filtered — it's how new products enter.
  • OCC-aware retry cron. The stuck-pending scan (>10 min) is bounded by a createdAt window so it never reads the range live inserts are writing — the unbounded version made every incoming webhook an OCC conflict with the cron.

Retry and cleanup

  • Retry cron every 5 minutes: stuck pending recovered, failed events retried with 2^retryCount minute backoff up to 5 attempts, then dead-letter.
  • Cleanup cron (03:00 UTC): processed/pending events older than 30 days deleted with their payload rows, bounded batches.

Registering webhooks

The dashboard Webhooks page manages the required topics against the current deployment URL. Deploy the backend first, then register topics — registrations pointing at a not-yet-deployed route get dropped by Shopify after repeated 404s.

On this page