Skip to content

Commerce applications / Case 13

Tracing a Fly.io Shopify app 500 error back to an invalid Admin API token

A custom commerce flow can look like a generic server failure when the real fault is one expired or invalid platform credential deep in an API request.

LaravelShopify APIFly.ioAuthenticationLogging

What this solved for the business or user

The checkout-related service stopped responding correctly. The fix was not to change the storefront; it was to restore the application's Shopify authorization and make future failures easier to diagnose from logs instead of a blank 500 page.

What was happening

A Laravel application hosted on Fly.io handled Shopify-facing business logic. It began returning 500 errors because the Admin API token being used by the application was no longer valid. Since the customer-facing symptom was only a server error, the important work was isolating whether the fault came from request data, Shopify authentication, the Draft Order mutation, or the application itself.

Why the obvious solution was not enough

The application processed order-related data, so failure handling needed to be explicit. Retrying blindly or partially creating an order could be worse than stopping the request with a clear server-side error.

How the solution works

  1. Start from Fly.io and application logs to identify the failing external call instead of debugging theme JavaScript first.
  2. Confirm the shop context and API credential used for the request before changing application logic.
  3. Replace the invalid Admin API token in the application's secret or environment configuration and redeploy the service.
  4. Keep request validation ahead of the Shopify mutation so missing shop data, malformed line items, and authentication failures remain distinguishable.
  5. Log the normalized payload and Shopify response at useful boundaries without logging secrets or sensitive customer data.
  6. Return structured application errors for invalid input and external API failures so the storefront can show a recoverable message instead of a generic broken flow.
Fail clearly around the external API boundary
if (!$shop || !$accessToken) {
    return response()->json(['error' => 'Shop configuration missing'], 422);
}

try {
    $result = $shopify->graphql($mutation, $variables);
    Log::info('draft_order_result', ['shop' => $shop, 'ok' => !isset($result['errors'])]);
} catch (Throwable $error) {
    Log::error('shopify_request_failed', ['shop' => $shop, 'message' => $error->getMessage()]);
    return response()->json(['error' => 'Unable to prepare checkout'], 502);
}

What should be verified before shipping

  • Verify the deployed app is using the intended token and shop after secret rotation.
  • Test missing shop, invalid line item, invalid quantity, invalid token, and successful API responses separately.
  • Confirm logs identify the failing boundary without exposing access tokens.
  • Verify the storefront receives a useful failure state and does not continue to an incomplete order.
  • Place a successful test order after the credential fix to confirm the full application path is restored.

What changed

The service returned to normal after the Admin API credential was corrected, and the debugging path reinforced a clearer separation between validation, platform authentication, Shopify mutation errors, and application exceptions.

Reusable lesson

When a custom app sits between the storefront and a commerce platform, observability is part of the feature. A 500 should be traceable to the exact external boundary that failed.