A deterministic demo harness for three Shopify webhook demonstrations used in technical meetup talks.
This demo harness supports three key demonstrations:
- Setup Demo: Use Hookdeck CLI to create production and development connections with deduplication and topic filtering
- Backpressure Demo: Generate high-volume simulated webhooks to demonstrate backpressure handling
- Logs + Retry Demo: Production deployment with payload-based failures, local debugging via CLI connection, and retry functionality
- Node.js (v18 or higher)
- Hookdeck API key (get from https://dashboard.hookdeck.com/settings/project/api-keys)
- Shopify CLI installed and authenticated
- A publicly accessible URL for the destination server (e.g., Vercel deployment, mock.hookdeck.com, or ngrok)
-
Install dependencies:
npm install cd shopify && npm install && cd ..
-
Configure environment variables: Create a
.envfile in the project root:# Required: Hookdeck API key HOOKDECK_API_KEY=your_hookdeck_api_key # Required: Destination URL (must be publicly accessible) # Can be Vercel deployment, mock.hookdeck.com, or any public URL DESTINATION_URL=https://your-url.com/webhook # Optional: Hookdeck source URL (automatically added/updated by upsert script) # The script will automatically add or update this after step 4 HOOKDECK_SOURCE_URL=https://hkdk.events/your-source-id # Optional: Shopify client secret (automatically added/updated by upsert script) # The script will automatically add or update this after step 4 # This is used by the simulation script for HMAC signature generation SHOPIFY_CLIENT_SECRET=your_shopify_client_secret
-
Set up your Shopify app:
Run the Shopify CLI to create your app and generate
shopify.app.toml:cd shopify shopify app devIf you already have an app and want to reset it:
shopify app dev --reset
The
shopify app devcommand will:- Create a new app in your Partners account (or use existing if not using --reset)
- Generate
shopify.app.tomlwith yourclient_id - Set up authentication and environment variables
- Start the development server
Press
Ctrl+Cto stop the dev server after the app is created. -
Set up Hookdeck connections:
npm run upsert
Or:
ts-node scripts/01-hookdeck-upsert.ts
This script will:
-
Validate that
shopify/shopify.app.tomlexists -
Run
shopify app env show --pathto get your Shopify API secret (automatically uses the shopify directory) -
Create or update two Hookdeck connections:
shopify-orders-prod-conn- HTTP destination (for production)shopify-orders-dev-conn- CLI destination (for local debugging)
-
Both connections share the same source and filter rules
-
Extract the source URL from the API response
-
Automatically add or update
HOOKDECK_SOURCE_URLin your.envfile -
Automatically add or update
SHOPIFY_CLIENT_SECRETin your.envfile (for simulation script HMAC signatures) -
Inject order webhook subscriptions into
shopify/shopify.app.tomlwith the Hookdeck source URL -
Preserve existing webhook subscriptions (app/uninstalled, app/scopes_update)
Note: If order webhook subscriptions already exist, the script will prompt you to confirm before replacing them.
- Verify Shopify webhook configuration:
- Check that
shopify/shopify.app.tomlhas yourclient_idpopulated - Verify the order webhook subscriptions (all events starting with
orders/) point to your Hookdeck source URL - The webhook subscriptions are automatically configured by the upsert script
- Check that
Objective: Demonstrate setting up Hookdeck connections with deduplication and topic filtering for all order events.
Steps:
-
Run the Hookdeck upsert script:
npm run upsert
Or:
ts-node scripts/01-hookdeck-upsert.ts
-
Verify both connections appear in the Hookdeck dashboard:
- Production connection:
shopify-orders-prod-conn- Source:
shopify-orders - Destination:
shopify-orders-prod(HTTP, connection:shopify-orders-prod-conn) - Rules: Topic filter (all
orders/*events) and deduplication (X-Shopify-Event-Id)
- Source:
- Development connection:
shopify-orders-dev-conn- Source: Same as production (shared)
- Destination:
shopify-orders-dev(CLI) - Rules: Same as production
- Production connection:
-
Verify the
shopify/shopify.app.tomlfile has been updated- The script automatically injects order webhook subscriptions with the Hookdeck source URL
- The webhook subscriptions for all order events should now point to your Hookdeck source
- Existing webhook subscriptions (app/uninstalled, app/scopes_update) are preserved
Observable Behavior:
- Two connections appear in Hookdeck dashboard
- Source URL is valid and accessible
- Both connections show configured rules (filter + deduplication)
- Filter matches all
orders/*events (orders/create, orders/updated, orders/paid, etc.)
Narration Cue:
"We use the Hookdeck CLI to create two connections: one for production with an HTTP destination, and one for development with a CLI destination for local debugging. Both share the same source and have deduplication and topic filtering configured for all order events."
Objective: Demonstrate how high-volume traffic creates backpressure and how to relieve it by adjusting throughput limits.
Steps:
-
Ensure
DESTINATION_URLin.envpoints to a publicly accessible endpoint:# Can be Vercel deployment, mock.hookdeck.com, or ngrok DESTINATION_URL=https://your-url.com/webhook -
Update the production connection if the destination URL changed:
npm run upsert
-
Send a high-volume burst of simulated webhooks:
npm run send:simulated -- --burst 300
-
Show backpressure in the Hookdeck dashboard:
- Navigate to the
shopify-orders-prod-connconnection - Observe queued events
- Note the throughput limit (5 requests/second)
- Navigate to the
-
Relieve backpressure by increasing the throughput limit:
hookdeck connection upsert shopify-orders-prod-conn \ --destination-rate-limit 5 \ --destination-rate-limit-period second
-
Observe events clearing from the queue
Observable Behavior:
- Events queue up when throughput limit is reached
- Queue size increases during the burst
- After increasing the limit, events process and queue clears
Narration Cue:
"High-volume traffic creates backpressure when the throughput limit is reached. We can adjust the throughput limit in real-time to relieve the backpressure and process the queued events."
Objective: Demonstrate production failures, local debugging via CLI connection, fixing the issue, and retrying failed events.
Prerequisites:
- Production Shopify app deployed (e.g., Vercel, Railway)
- Production connection (
shopify-orders-prod-conn) configured with production URL - The production URL should point to your deployed Shopify app (webhooks are automatically routed)
Steps:
-
Deploy production Shopify app (if not already deployed):
- Deploy the Shopify app to your hosting platform (e.g., Vercel, Railway)
- The webhook handler in
shopify/app/routes/webhooks.shopify.orders.$.tsxassumescustomer.phoneexists (will fail if missing) - Update
DESTINATION_URLin.envto point to your deployed Shopify app URL (e.g.,https://your-app.vercel.app) - The webhook endpoint will be automatically routed to
/webhooks/shopify/ordersby the Shopify app framework - Note: The webhook handler at
shopify/app/routes/webhooks.shopify.orders.$.tsxwill handle all order webhook events - The upsert script automatically appends
/webhooks/shopify/ordersto the Hookdeck source URL inshopify.app.toml - Run
npm run upsertto update the production connection
-
Send events that trigger failures:
# Send events without customer.phone to trigger failures npm run send:simulated -- --burst 50 --no-customer-phone -
Show failures in Hookdeck dashboard:
- Navigate to the
shopify-orders-prod-connconnection's events/logs - Filter for failed events (status: 500)
- Observe the failed deliveries with error message "Missing customer.phone field"
- Navigate to the
-
Use CLI connection for local debugging:
# Start the Shopify app locally cd shopify shopify app dev
In another terminal:
# Use the CLI connection to receive events locally hookdeck listen 4000 shopify-orders -
Replicate the problem locally:
- Send a test event without customer.phone to the CLI connection
- Observe the failure in local server logs
- Confirm the issue: missing
customer.phonefield
-
Fix the issue locally:
- Edit
shopify/app/routes/webhooks.shopify.orders.$.tsxto check for phone number before callingsendConfirmationText() - Add a conditional check: only send confirmation text if
customer.phoneexists - Test with events that include customer.phone (should work)
- Test with events without customer.phone (should now handle gracefully)
- Verify the fix works
- Edit
-
Push fix to production:
- Commit the code changes
- Deploy the updated Shopify app to production
-
Retry failed events on production connection:
- In Hookdeck dashboard, select a single failed event
- Click "Retry" to test with one event
- Observe retry attempt succeeding
- Then select multiple failed events
- Click "Bulk Retry" to retry all failed events
- Observe all retry attempts succeeding
-
Verify all events are processed:
- Check that all previously failed events are now successful
- Show the event timeline in Hookdeck dashboard
Observable Behavior:
- Failed events appear in logs with 500 status and clear error message
- Filtering shows only failed events
- CLI connection allows local debugging and testing
- After fixing and deploying, retries succeed
- Bulk retry processes multiple events efficiently
Narration Cue:
"When production events fail, we can see them clearly in Hookdeck logs with specific error messages. We use the CLI connection to replicate the issue locally, fix it, and then retry the failed events. Hookdeck automatically retries with the fixed destination, ensuring no events are lost."
Creates or updates two Hookdeck connections and injects webhook subscriptions into shopify.app.toml:
shopify-orders-prod-conn- HTTP destination (for production)shopify-orders-dev-conn- CLI destination (for local debugging)
Both connections have:
- Shared webhook source (SHOPIFY type with webhook secret from Shopify CLI)
- Topic filter (all
orders/*events) - Deduplication (
X-Shopify-Event-Idheader) - Low throughput limit (5 req/s) for backpressure demos
Prerequisites:
shopify/shopify.app.tomlmust exist (created byshopify app dev)HOOKDECK_API_KEYenvironment variable setDESTINATION_URLenvironment variable set
Usage:
npm run upsertOr:
ts-node scripts/01-hookdeck-upsert.tsWhat it does:
- Validates that
shopify/shopify.app.tomlexists - Runs
shopify app env show --pathto getSHOPIFY_API_SECRETfrom the Shopify CLI (automatically uses the shopify directory) - Creates/updates Hookdeck connections via API
- Injects order webhook subscriptions into
shopify.app.toml - Preserves existing webhook subscriptions (app/uninstalled, app/scopes_update)
- Prompts for confirmation if order webhooks already exist
Output:
- Prints the Hookdeck source URL
- Updates
shopify/shopify.app.tomlwith order webhook subscriptions - Preserves all existing configuration
Sends simulated Shopify webhook requests to a Hookdeck source URL.
Usage:
# Using npm script
npm run send:simulated -- --burst 300 --topic orders/create --no-customer-phone
# Direct execution (defaults to orders/create)
ts-node scripts/02-send-simulated-webhooks.ts --burst 300Options:
--burst <number>: Number of webhooks to send (default: 300)--duplicate-every <number>: Reuse same event ID every N requests (default: 0 = unique IDs)--topic <string>: Webhook topic (default:orders/create)- Supported:
orders/create,orders/updated,orders/paid,orders/cancelled, etc.
- Supported:
--no-customer-phone: Exclude customer.phone from payload (for failure scenarios)--with-customer-phone: Include customer.phone in payload (default)
Environment Variables:
HOOKDECK_SOURCE_URL: Required - Hookdeck source URL (base URL, the script automatically appends/webhooks/shopify/orders). Automatically set by the upsert script.SHOPIFY_CLIENT_SECRET: Required - Client secret for HMAC signature generation (should match what 'shopify app dev' uses). Automatically set by the upsert script.BURST_SIZE: Optional - Default burst sizeDUPLICATE_EVERY: Optional - Default duplicate frequencyTOPIC: Optional - Default topicINCLUDE_CUSTOMER_PHONE: Optional - Set to "false" to exclude customer.phone (default: true)
The order webhook handler in the Shopify app. For orders/create events, it calls sendConfirmationText() to send a confirmation text message to the customer. This function will throw an error if customer.phone is missing, causing a 500 response. This is intentional for the demo - you'll fix it during Demo 3.
Location: shopify/app/routes/webhooks.shopify.orders.$.tsx
Webhook Path: /webhooks/shopify/orders (appended to Hookdeck source URL in shopify.app.toml)
Behavior:
- For
orders/createevents: CallssendConfirmationText(customer.phone)which throws if phone is missing - Returns 200 if processing succeeds
- Returns 500 if
customer.phoneis missing or null (fororders/createevents) - During Demo 3, you'll add a phone number check before calling
sendConfirmationText()
Usage:
The webhook handler is automatically invoked when the Shopify app receives order webhooks. To test locally:
cd shopify
shopify app devThen use the CLI connection to forward webhooks to your local app:
hookdeck listen 4000 shopify-ordersWebhook Endpoint:
POST /webhooks/shopify/orders: Webhook delivery endpoint (handled byshopify/app/routes/webhooks.shopify.orders.$.tsx)
npm install -g hookdeck
hookdeck loginnpm install -g @shopify/cli @shopify/theme
shopify auth loginEnsure your destination server is publicly accessible. Options:
- Deploy to Vercel, Railway, or similar
- Use
https://mock.hookdeck.comfor testing - Use ngrok for local testing:
ngrok http 4000
- Verify
HOOKDECK_API_KEYis set in your.envfile - Check that
DESTINATION_URLis set and valid - Ensure the destination URL is publicly accessible
- Verify that
shopify/shopify.app.tomlexists (runshopify app devfirst) - Ensure you're authenticated with Shopify CLI (run
shopify auth loginif needed)
- Verify the destination URL in the Hookdeck connection matches your server
- Check that the server is running and accessible
- Review Hookdeck logs for delivery errors
- Verify the
shopify-orders-dev-connconnection exists in Hookdeck dashboard - Ensure you're using the correct source name:
hookdeck listen 4000 shopify-orders - Check that the local server is running on port 4000 (configured in
shopify/shopify.web.toml)
ISC