Billing Redirect Not Working On Remix - 401 Error using "billing.request()"

I’m using RemixJS, the latest versions of** @Shopify_77 /shopify-app-remix**

I first noticed this error when the app reviewer declined my app since I didn’t have proper “Upgrade/Downgrade” buttons. I was very confused since this works flawlessly on the test store. I even provided a full video of it working, twice.

After debugging logs, I couldn’t find ANYTHING.

I have two plans declared on my “shopify.server.ts”. under the billing object which are available on “availablePlans”. and “isBillingTest()” just checks my .env variable for TEST_BILLING === ‘true’

“test: false” in this case right now.

When I click “Subscribe”, my code executes here:

export const action = async ({ request }: ActionFunctionArgs) => {
  const adminContext = await authenticate.admin(request);
  const { billing, session } = adminContext;

const { hasActivePayment, appSubscriptions } = await billing.check({
    plans: availablePlans,
    isTest: isBillingTest(),
  });

if (!hasActivePayment) {
try { 
await billing.require({
        plans: availablePlans,
        onFailure: async (error) => {
          console.error('Billing require failed:', error);
          console.log('Attempting to redirect to billing page for plan:', plan);
          return billing.request({
            plan: plan.toString() as PlanName,
            isTest: isBillingTest(),
            returnUrl: createReturnUrl(session.shop),
          });
        },
      });    
} catch ( error )  {
   console.log('error billing', error );
}
}

and this is the error:

Response {
  size: 0,
  [Symbol(Body internals)]: {
    body: null,
    type: null,
    size: 0,
    boundary: null,
    disturbed: false,
    error: null
  },
  [Symbol(Response internals)]: {
    url: undefined,
    status: 401,
    statusText: 'Unauthorized',
    headers: {
      'x-shopify-api-request-failure-reauthorize-url': 'https://xxxxxx.myshopify.com/admin/charges/123345/21345/RecurringApplicationCharge/confirm_recurring_application_charge?signature=BAh7BzoHaWRsKwg'
    },
    counter: 0,
    highWaterMark: undefined
  }
}

It’s very weird. Why am I getting 401 errors? I wonder if this what the reviewer ran into too.

Although, when I change test: true then I can make successful tests.

Any ideas would be greatly appreciated.

Here is another clue for the Shopify team: On this app, I always had it set to billing test: false. I decided to run a “test payment” (because I’m like "why was the reviewer getting an error? Maybe something on production?), so I changed test: true. Subscription worked. No issues. Webhooks ran.

I then changed it BACK to test: false. I go to subscribe, and I’m expecting it to redirect to the charges URL, but no, 401. This error only started happening AFTER I changed it from the original test: false → true → false.

Now, I don’t know what else to do. I’m afraid the reviewer is going to look again and say it failed. This seems to be on the Shopify side I’m assuming. If I manually enter that URL from the response “x-shopify-api-request-failure-reauthorize-url” then it does go to the Approval Page.

Here is the Shopify App Reviewer getting a 401. This is from my server logs. Redacted information.

[shopify-app/INFO] Requesting billing | {shop: 1234.myshopify.com, plan: Starter Plan, isTest: false, returnUrl: https://admin.shopify.com/store/1234/apps/my-app/app/billing}
POST /app/billing?_data=routes%2Fapp.billing 401 - - 735.172 ms

But somehow after this the webhook ran for subscription? How is this possible if they didn’t get redirected to an approval page?

app subscription {
  admin_graphql_api_id: 'gid://shopify/AppSubscription/1234',
  name: 'Pro Plan',
  status: 'ACTIVE',
  admin_graphql_api_shop_id: 'gid://shopify/Shop/1234',
  created_at: '2025-07-03T20:35:51+08:00',
  updated_at: '2025-07-03T20:35:57+08:00',
  currency: 'USD',
  capped_amount: null,
  price: '49.00',
  interval: 'every_30_days',
  plan_handle: null
}
POST /webhooks/app/subscriptions_update 200 - - 290.793 ms

I am also running into this, have you managed to solve it?

I had a similar issue caused by wrapping billing.requirein a try-catch statement. The issue is that sometimes billing.require throws a redirect, which if you catch (and log) won’t get thrown.

The key I think is to rethrow inside your catch statement:

try { 
    await billing.require({
        plans: availablePlans,
        onFailure: async (error) => {
          console.error('Billing require failed:', error);
          console.log('Attempting to redirect to billing page for plan:', plan);
          return billing.request({
            plan: plan.toString() as PlanName,
            isTest: isBillingTest(),
            returnUrl: createReturnUrl(session.shop),
          });
        },
    });    
} catch ( error )  {
    console.log('error billing', error );
    // re-throw error in case it's a redirect
    throw error;
}

Now, I totally acknowledge this is not ideal. What would be better is to work out whether error is (redirect) response, or an error. If it’s the latter, log etc. If it’s the former, rethrow so the user ends up on the right page.

This might be related to the 401 auth error.

Hey Alexz ,
Does you find this solutions??

Yes @Rohan007 the solution I posted worked for me.