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.
Plain-English summary
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.
01 / Real situation
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.
02 / Constraint
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.
03 / Implementation
How the solution works
- Start from Fly.io and application logs to identify the failing external call instead of debugging theme JavaScript first.
- Confirm the shop context and API credential used for the request before changing application logic.
- Replace the invalid Admin API token in the application's secret or environment configuration and redeploy the service.
- Keep request validation ahead of the Shopify mutation so missing shop data, malformed line items, and authentication failures remain distinguishable.
- Log the normalized payload and Shopify response at useful boundaries without logging secrets or sensitive customer data.
- 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.
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);
}04 / Release checks
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.
05 / Result
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 lessonWhen 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.
