dubinc/dub · error

Failed to fetch user info from Dub.

Error message

Failed to fetch user info from Dub.

What it means

getUserInfo calls Dub's /workspaces endpoint with the stored access token to fetch the user's workspace; if the response is not OK it throws this fixed message with the API error attached as `cause`. It is used to validate that a stored token still works before returning it from getValidToken.

Source

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

    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();

  if (!response.ok) {
    throw new Error("Failed to fetch user info from Dub.", {
      cause: data.error,
    });
  }

  const { workspace } = data as { workspace: Workspace };

  return workspace;
}

// If the token is expired, it will refresh it
export async function getValidToken({ stripe }: { stripe: Stripe }) {
  const token = await getSecret<Token>({
    stripe,
    name: "dub_token",
  });

  if (!token) {
    throw new Error("Access token not found for the account.");

View on GitHub (pinned to f216b94a24)

Solutions

  1. Inspect error.cause for the API status/message (typically 401 unauthorized).
  2. Trigger token refresh: getValidToken already catches this and calls refreshToken — ensure that path runs.
  3. If refresh also fails, have the user reconnect Dub from the Stripe App to issue a new token.
  4. Check Dub API status if the cause indicates 5xx/network issues.

Example fix

// before
// token assumed valid every call
// after
try { await getUserInfo({ token }); } catch { token = await refreshToken({ token }); }
Defensive patterns

Strategy: retry

Validate before calling

const token = await getSecret({ stripe, name: "dub_token" });
if (!token?.access_token) throw new Error("no Dub token stored; run the connect flow first");

Type guard

function isWorkspacePayload(data: unknown): data is { workspace: { id: string } } {
  return typeof data === "object" && data !== null && "workspace" in data && typeof (data as any).workspace?.id === "string";
}

Try / catch

try {
  const info = await getUserInfo({ token });
} catch (e) {
  if (e instanceof Error && e.cause && /unauthorized|401/i.test(JSON.stringify(e.cause))) {
    const fresh = await refreshToken({ token });
    return getUserInfo({ token: fresh });
  }
  if (e instanceof Error && e.cause && /5\d\d/.test(JSON.stringify(e.cause))) {
    await sleep(1000); return getUserInfo({ token }); // retry transient
  }
  throw e;
}

Prevention

When it happens

Trigger: getValidToken → getUserInfo with an access token that the Dub API rejects (revoked, expired, or malformed) or when the workspace fetch request otherwise fails (network/5xx).

Common situations: User revoked Dub's access from their Dub workspace settings; token fetched from Stripe secrets was saved under an older OAuth scope; Dub API incident during a Stripe App session/token call.

Related errors


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