Post-Purchase Extension: ShouldRender returns true but Render phase never executes (production only)

Environment

  • Shopify CLI version: Latest (using npx shopify app deploy)
  • Extension API version: 2024-07
  • Store type: Production store (Plus plan)
  • Payment methods affected: Shop Pay with credit card, regular credit card

Problem Description

My post-purchase extension works correctly in the development environment but fails silently in production. The ShouldRender phase executes successfully, but the Render phase never runs.

What’s happening:

  1. Customer completes checkout
  2. My backend receives the /offer API call from ShouldRender (confirmed via server logs)
  3. Backend returns a valid offer with { render: true }
  4. storage.update({ offer }) is called before returning
  5. The post-purchase page never appears - customer goes directly to thank you page
  6. No errors in any logs

Evidence from server logs:

[requestId] POST /offer received
[requestId] Offer prepared: shop=mystore.myshopify.com, orderId=xxx, product=Product Name
Sending offer with changes: [{"type":"add_variant","variantID":12345,...}]
{"method":"POST","path":"/offer","status":200,"duration":4071}

The /offer endpoint returns 200 with a valid offer, but my /render endpoint (called in the Render phase) is never hit.

Code Structure

ShouldRender phase:

extend("Checkout::PostPurchase::ShouldRender", async ({ inputData, storage }) => {
  try {
    const response = await fetch(`${API_BASE_URL}/offer`, {
      method: "POST",
      headers: { "Content-Type": "application/json" },
      body: JSON.stringify({ token: inputData.token }),
    });

    if (!response.ok) {
      return { render: false };
    }

    const data = await response.json();
    const { offer } = data;

    if (!offer) {
      return { render: false };
    }

    // Store the offer for the Render phase
    await storage.update({ offer });
    return { render: true };
  } catch (error) {
    console.error("Error in ShouldRender:", error);
    return { render: false };
  }
});

Render phase:

render("Checkout::PostPurchase::Render", () => <PostPurchaseOffer />);

function PostPurchaseOffer() {
  const { storage, inputData, done } = useExtensionInput();
  const [offer, setOffer] = React.useState(null);

  React.useEffect(() => {
    async function loadOffer() {
      const initialData = await storage.initialData;
      if (initialData?.offer) {
        setOffer(initialData.offer);
        // Notify backend that render executed
        await fetch(`${API_BASE_URL}/render`, { /* ... */ });
      }
    }
    loadOffer();
  }, []);

  // ... rest of component
}

What I’ve verified:

  • Extension deployed and active (confirmed in Partner Dashboard)
  • Post-purchase page enabled in Settings → Checkout
  • “Access post-purchase extensions” - Full access granted
  • “Request for Checkout Extension” - Granted
  • App installed on production store
  • network_access = true in shopify.extension.toml
  • Backend returning valid response with offer data
  • Multiple payment methods tested (Shop Pay, credit card)
  • Same currency as store default

Questions:

  1. Is there a way to debug why Shopify decides not to execute the Render phase after ShouldRender returns { render: true }?
  2. Are there any platform-level restrictions that would cause this behavior silently?
  3. Is there a known issue with storage.update() data not persisting between ShouldRender and Render in production?

Any guidance would be greatly appreciated!