Gareth Wilson Gareth Wilson

Building post-purchase AI follow-up with Shopify and Claude

Published


A customer completes a $284 order in your Shopify store. Within seconds of the order being paid, Claude has read the line items, identified the customer as a first-time buyer, drafted a thank-you email that mentions the specific products (not "your recent order"), included care instructions for the items from your product database, suggested a complementary item and scheduled a satisfaction check-in for ten days from now, when the items will have arrived and the customer will have used them. The email goes out through your transactional email service, the suggested product gets logged for the retargeting feed, and the customer's profile in your CRM gets tagged "first-time-buyer, home-and-kitchen, brewing-enthusiast" for future campaigns.

That's the post-purchase workflow most ecommerce teams are now building with Shopify and an LLM. The Shopify side is well-documented — orders/paid and orders/create webhooks fire reliably. The Claude side is straightforward. The hard part is the middle: making sure every paid order triggers exactly one follow-up (not zero, not three), surviving Black Friday spikes, and being able to replay an entire day of orders when your follow-up prompt gets a key update.

This guide walks through that glue layer end to end: the architecture, seven concrete steps to wire it up, and the production concerns that show up the moment you're running real ecommerce volume.

The flow

flowchart TB
    A[Customer completes<br/>checkout] --> B[Shopify webhook<br/>orders/paid]
    B -->|POST JSON| C[Hookdeck<br/>inbound source]
    C -->|dedupe + transform<br/>+ rate limit + retry| D[Claude<br/>follow-up handler]
    D -.->|messages API| E[claude-sonnet-4]
    D -->|POST follow-up plan| F[Hookdeck<br/>callback source]
    F -->|route| G1[Transactional<br/>email service]
    F -->|route| G2[CRM<br/>profile update]
    F -->|route| G3[Retargeting<br/>feed]
    F -->|route| G4[Scheduled<br/>check-in job]

There are two webhook flows that need to be reliable:

  1. Shopify's orders/paid webhook into the AI step — is mission-critical. A missed event means a customer gets no follow-up, doesn't receive care instructions for a fragile product, doesn't see a thank-you, doesn't get the complementary product suggestion. In commerce, a missed event is lost revenue.
  2. The AI's outputs back into your email service, CRM, retargeting feed, and check-in scheduler — must reach all of them reliably even when one is briefly down. The customer should still get their thank-you email even if the retargeting feed is offline.

Most teams build this with a POST /webhooks/shopify endpoint that calls Claude inline, then calls each downstream API in sequence. That's enough to demo. It's not enough to run on Black Friday, and the reasons it isn't are the reasons Hookdeck sits in the middle.

What you'll need

  • A Shopify store with admin access to register webhooks
  • An Anthropic API key with access to a Claude model
  • A Hookdeck Event Gateway account — the free tier covers this workflow at low volume
  • Hookdeck CLI installed: npm install hookdeck-cli -g or brew install hookdeck/hookdeck/hookdeck
  • A handler endpoint that receives the order payload, calls Claude, and POSTs the result back
  • Destinations for the outputs — a transactional email service (Postmark, SendGrid, Resend), your CRM, your retargeting/ads feed

Step 1: Create the Hookdeck Event Gateway source for Shopify

In the Hookdeck Event Gateway dashboard:

  • Create Connection → New Source
  • Type: Shopify — Hookdeck Event Gateway has a pre-configured Shopify source that handles signature verification automatically (see the Shopify webhooks guide)
  • Name: shopify-orders-paid
  • Provide your Shopify webhook signing secret

Copy the generated source URL.

Step 2: Register the Shopify webhook

In Shopify Admin:

  • Settings → Notifications → Webhooks → Create webhook
  • Event: Order paid
  • Format: JSON
  • URL: paste the Hookdeck source URL
  • Webhook API version: latest stable

Save. Place a test order (or use Shopify's "Send test notification" button). The full order payload should appear in the Hookdeck Event Gateway dashboard within a second.

If you have multiple stores, register one webhook per store pointing at the same Hookdeck source. The transformation step in step 4 can add a store_id field so you can identify the source downstream.

Step 3: Add the destination — your Claude follow-up handler

The handler:

  1. Receives the parsed order payload
  2. Optionally enriches with product data (care instructions, complementary product mappings) and customer history (first-time vs returning)
  3. Calls Claude with a follow-up generation prompt
  4. POSTs the result to a second Hookdeck source for fan-out

A minimal Cloudflare Worker:

export default {
  async fetch(request, env) {
    const order = await request.json();

    // Enrich with internal data
    const [customerHistory, productData] = await Promise.all([
      fetch(`${env.INTERNAL_API}/customers/${order.email}/history`, {
        headers: { authorization: `Bearer ${env.INTERNAL_TOKEN}` },
      }).then(r => r.json()),
      fetch(`${env.INTERNAL_API}/products/bulk`, {
        method: 'POST',
        headers: {
          authorization: `Bearer ${env.INTERNAL_TOKEN}`,
          'content-type': 'application/json',
        },
        body: JSON.stringify({
          ids: order.line_items.map(li => li.product_id),
        }),
      }).then(r => r.json()),
    ]);

    const response = await fetch('https://api.anthropic.com/v1/messages', {
      method: 'POST',
      headers: {
        'x-api-key': env.ANTHROPIC_API_KEY,
        'anthropic-version': '2023-06-01',
        'content-type': 'application/json',
      },
      body: JSON.stringify({
        model: 'claude-sonnet-4-5',
        max_tokens: 2048,
        system: FOLLOW_UP_PROMPT,
        messages: [{
          role: 'user',
          content: JSON.stringify({
            order_id: order.order_id,
            customer_email: order.email,
            customer_first_name: order.first_name,
            is_first_purchase: customerHistory.total_orders === 1,
            line_items: order.line_items,
            product_details: productData,
            order_value: order.total_price,
            currency: order.currency,
          }),
        }],
      }),
    });

    const result = await response.json();
    const followUp = JSON.parse(result.content[0].text);

    await fetch(env.HOOKDECK_CALLBACK_URL, {
      method: 'POST',
      headers: { 'content-type': 'application/json' },
      body: JSON.stringify({
        order_id: order.order_id,
        customer_email: order.email,
        follow_up: followUp,
      }),
    });

    return new Response('ok', { status: 200 });
  },
};

Configure the Hookdeck Event Gateway destination:

  • Type: HTTP
  • URL: your handler URL
  • Authentication: an HTTP header carrying a shared secret

Step 4: Add transformation, dedupe, and rate-limit rules

Transformation — flatten Shopify's verbose order payload to the fields your handler actually uses, and add a stable order_id you'll use for deduplication downstream:

addHandler('transform', (request, context) => {
  const order = request.body;

  request.body = {
    order_id: String(order.id),
    order_number: order.order_number,
    email: order.email,
    first_name: order.customer?.first_name,
    last_name: order.customer?.last_name,
    total_price: order.total_price,
    currency: order.currency,
    line_items: order.line_items.map(li => ({
      product_id: String(li.product_id),
      variant_id: String(li.variant_id),
      title: li.title,
      quantity: li.quantity,
      price: li.price,
      sku: li.sku,
    })),
    shipping_country: order.shipping_address?.country_code,
    paid_at: order.processed_at,
  };

  return request;
});

Dedupe — Shopify can fire the same webhook more than once during retries. Configure the Hookdeck Event Gateway destination to use the order_id as an idempotency key. With idempotent delivery enabled, your handler sees each order exactly once, and the AI step doesn't run twice:

  • Idempotency key: body.order_id
  • Idempotency window: 24 hours

This is the single most important configuration in this article. Without it, every retry by Shopify becomes another Claude call, another email to the customer, another tag on the CRM.

Rate limit — protect your Claude rate limit during Black Friday and seasonal spikes:

  • Rate: 15 per second
  • Burst: 40

Hookdeck Event Gateway queues the rest. Every order still gets followed up; you just don't blow your concurrency budget.

Retry policy — aggressive on the inbound leg, because losing an order means a missed customer follow-up:

  • Initial delay: 30 seconds
  • Max attempts: 20
  • Max age: 48 hours
  • Apply on status codes: 408, 429, 500, 502, 503, 504, 529

Step 5: Test the inbound leg locally with the CLI

Route the inbound connection to the CLI:

hookdeck login
hookdeck listen 3000 shopify-orders-paid

A local inspector:

// inspect.js
const http = require('http');
http.createServer((req, res) => {
  let body = '';
  req.on('data', chunk => body += chunk);
  req.on('end', () => {
    console.log('Canonical order:', JSON.parse(body));
    res.writeHead(200);
    res.end('ok');
  });
}).listen(3000);

Place a test order. Verify the canonical payload looks right — particularly that the line items are flattened correctly and the order_id is stable. Iterate on the transformation; press r to replay the most recent event when you tweak the prompt or shape.

Step 6: Wire follow-up outputs back through Hookdeck

The follow-up plan fans out to several downstreams:

  • Transactional email service — send the drafted thank-you email
  • CRM — update the customer profile with the new tags and segmentation
  • Retargeting feed — log the suggested complementary product
  • Scheduled check-in job — schedule a 10-day satisfaction check email

Create a second connection with the source post-purchase-follow-ups and one destination per downstream.

For the email service (Resend in this example), a transformation builds the API call:

addHandler('transform', (request, context) => {
  const { customer_email, follow_up } = request.body;

  request.url = 'https://api.resend.com/emails';
  request.method = 'POST';
  request.headers = {
    ...request.headers,
    authorization: `Bearer ${context.secrets.RESEND_API_KEY}`,
    'content-type': 'application/json',
  };
  request.body = {
    from: 'orders@acme.com',
    to: customer_email,
    subject: follow_up.email.subject,
    html: follow_up.email.html_body,
    tags: [
      { name: 'category', value: 'post-purchase' },
      { name: 'is_first_purchase', value: String(follow_up.is_first_purchase) },
    ],
  };

  return request;
});

For the scheduled check-in, the destination might be your job-scheduler service (e.g. Inngest, Trigger.dev) with a delay. For the retargeting feed and the CRM update, similar transformations build the right API call.

Retry policy on these callbacks:

  • Initial delay: 15 seconds
  • Max attempts: 15
  • Max age: 72 hours

Each connection retries independently. If your CRM is briefly down, only the CRM connection retries — the customer's email goes out on schedule.

Step 7: Run the full chain end to end

Place a real test order in your store. You should see, in order:

  1. Shopify fires orders/paid into shopify-orders-paid
  2. Hookdeck Event Gateway verifies, dedupes, transforms, and delivers to your handler
  3. Your handler enriches, calls Claude, and POSTs to post-purchase-follow-ups
  4. The transactional email service sends the email
  5. The CRM updates the customer profile
  6. The retargeting feed logs the suggested product
  7. The check-in scheduler enqueues a 10-day reminder

Place the same test order twice (cancel and re-trigger). Confirm the customer receives exactly one email — Hookdeck Event Gateway's idempotency rule caught the duplicate.

If anything fails, the Hookdeck Event Gateway dashboard tells you exactly where, with payload and response visibility at every hop.

Why Hookdeck and not just a try/except in your app server?

Three properties of commerce workflows make a direct integration the wrong choice once you're past the demo:

Commerce events are mission-critical. A missed orders/paid webhook means a missed customer follow-up, which means a customer who doesn't hear from you after their first purchase. The revenue impact compounds — lost cross-sell, lost loyalty, lost subscription upsell. Hookdeck Event Gateway's aggressive retry and full event storage mean an event that arrives is an event that gets processed with a clear audit trail.

Shopify retries duplicates; your AI step must be idempotent. Shopify guarantees at-least-once delivery. If your endpoint times out, it'll fire again. Without idempotent delivery on the AI step, every duplicate means another Claude call, another email, another CRM update. Hookdeck Event Gateway's idempotency rules deduplicate on a key you choose (order ID in this case) and your handler runs once per unique order.

You'll want to replay the AI step every time the prompt changes. Ecommerce follow-up prompts evolve constantly — new tone tests, new product mappings, seasonal variants, A/B copy. Hookdeck's replay lets you re-run the last week of orders through a new prompt against a different destination (e.g. a "preview" handler) before flipping the live one over. No backfill scripts, no risk to live customers.

You can build all of this on your own: a durable queue with idempotency, retry workers, a transformation step, a payload store, an observability layer, a replay tool. That's the work Hookdeck Event Gateway collapses into a connection in a dashboard. The hours you don't spend on infrastructure are hours you spend on the prompt and the customer experience.

Going to production

Observability for the merchandising team. Hookdeck's Issues feature surfaces failure patterns automatically. A spike in email-service 429s on Black Friday is something the merchandising team should know about while the sale is still running, not in the post-mortem.

Plan for Black Friday. The rate limits and retry policies you set in step 4 should be tuned for your peak day, not your average day. Test the pipeline at 10× your normal volume before the season.

Replay the prompt thoughtfully. When you change the follow-up prompt, you have three choices: only apply to new orders (no replay), replay everything from the last N days to the live system (customers get a second email, usually not what you want), or replay to a preview/log destination to validate the change. The third option is almost always the right one.

Handle PII responsibly. Order payloads contain addresses, phone numbers, and sometimes payment metadata. Configure Hookdeck's payload redaction on sensitive fields before going live.

What to build next

This pattern generalizes: Apply it to abandoned-cart recovery, restock notifications, subscription renewal upsells, refund-request triage, or VIP-customer flagging. The plumbing stays the same; the prompts and the schemas change.

If you're building any of this, the fastest way to get past the demo phase is to stop maintaining your own webhook infrastructure. Start with the Hookdeck free tier (you can run this entire workflow without paying anything until you hit real volume) and use the CLI to keep your development loop fast.


Gareth Wilson

Gareth Wilson

Product Marketing

Multi-time founding marketer, Gareth is PMM at Hookdeck and author of the newsletter, Community Inc.