dubinc/dub · error

data.error.message

Error message

data.error.message

What it means

getToken exchanges the Stripe account's OAuth code with Dub for an access token; on a non-OK response it throws with the message from the OAuth provider's error payload. Note the outer catch only logs the message ("Unable to retrieve Dub access token: ...") and swallows the error, returning undefined to callers.

Source

Thrown at packages/stripe-app/src/utils/oauth.ts:60

  try {
    const response = await fetch(`${DUB_API_HOST}/oauth/token`, {
      method: "POST",
      headers: {
        "Content-Type": "application/x-www-form-urlencoded",
      },
      body: new URLSearchParams({
        client_id: DUB_CLIENT_ID,
        redirect_uri: getRedirectUrl(mode),
        grant_type: "authorization_code",
        code_verifier: verifier,
        code,
      }),
    });

    const data = await response.json();

    if (!response.ok) {
      throw new Error(data.error.message);
    }

    return data as Token;
  } catch (e) {
    console.error("Unable to retrieve Dub access token:", (e as Error).message);
  }
}

// Returns the user info from Dub using the access token
export async function getUserInfo({ token }: { token: Token }) {
  const response = await fetch(`${DUB_API_HOST}/oauth/userinfo`, {
    headers: {
      Authorization: `Bearer ${token.access_token}`,
    },
  });

  const data = await response.json();

View on GitHub (pinned to f216b94a24)

Solutions

  1. Check the logged message ("Unable to retrieve Dub access token: ...") for the provider's error (e.g. invalid_grant).
  2. Have the user restart the connect flow to get a fresh authorization code (codes are single-use).
  3. Verify DUB_CLIENT_ID / DUB_CLIENT_SECRET and the registered redirect URI match the deployed Stripe App.
  4. Fix the code path so getToken's throw isn't swallowed and callers handle undefined tokens.

Example fix

// before
} catch (e) { console.error("Unable to retrieve Dub access token:", (e as Error).message); }
// after
} catch (e) { console.error(...); throw e; } // propagate so caller can prompt reconnect
Defensive patterns

Strategy: try-catch

Validate before calling

if (!process.env.DUB_CLIENT_ID || !process.env.DUB_CLIENT_SECRET) {
  throw new Error("DUB_CLIENT_ID and DUB_CLIENT_SECRET must be configured");
}
if (!code || typeof code !== "string") throw new Error("OAuth code missing from connect callback");

Type guard

function isTokenResponse(data: unknown): data is { access_token: string; refresh_token: string } {
  return typeof data === "object" && data !== null && "access_token" in data;
}

Try / catch

try {
  const token = await getToken({ code });
  if (!token) throw new Error("token exchange returned nothing — check logs for the underlying error");
  await saveSecret({ stripe, name: "dub_token", value: token });
} catch (e) {
  console.error("Dub connect failed:", (e as Error).message);
  return showReconnectUi();
}

Prevention

When it happens

Trigger: The Dub OAuth token endpoint returns 4xx/5xx during the Stripe App install/connect flow: invalid or already-consumed authorization `code`, client id/secret mismatch, or redirect_uri mismatch.

Common situations: User refreshes the connect page causing code reuse; DUB_CLIENT_SECRET env misconfigured in the Stripe App deployment; redirect URI registered in Dub dev portal doesn't match the deployed one.

Related errors


AI-assisted analysis of dubinc/dub@f216b94a24 (2026-08-31). Data as JSON: /api/errors/5e12c7f91047f027. Report an issue: GitHub.