GitbookIO/gitbook · error · SiteOAuthConsentError

OAuth server ${endpoint} responded with ${response.status}

Error message

OAuth server ${endpoint} responded with ${response.status}

What it means

postToConsentEndpoint throws SiteOAuthConsentError with the upstream HTTP status when the external OAuth consent server answers a non-2xx. The message names the endpoint and the status, distinguishing transport-level rejection (this error) from later consent-decision failures.

Source

Thrown at packages/gitbook/src/lib/site-oauth/index.ts:122

    const signature = createHmac('sha256', GITBOOK_SITE_OAUTH_SIGNING_SECRET)
        .update(`${siteId}:${timestamp}:${rawBody}`)
        .digest('hex');

    const url = new URL(GITBOOK_OAUTH_SERVER_URL);
    url.pathname += `/${encodeURIComponent(siteId)}/${endpoint}`;
    const response = await fetch(url, {
        method: 'POST',
        headers: {
            'content-type': 'application/json',
            'x-gitbook-signature': signature,
            'x-gitbook-timestamp': String(timestamp),
        },
        body: rawBody,
        cache: 'no-store',
    });

    if (!response.ok) {
        throw new SiteOAuthConsentError(
            `OAuth server ${endpoint} responded with ${response.status}`,
            response.status
        );
    }

    return (await response.json()) as T;
}

View on GitHub (pinned to db67585ee2)

Solutions

  1. Read the status in the message: 4xx → fix the OAuth client config (client_id, secret, redirect URIs) at the provider; 5xx → check the consent service health and retry
  2. Verify the consent endpoint URL in your site OAuth configuration is current and reachable from the deployment
  3. Re-initiate the consent flow so a fresh consent token is used if the old one expired
  4. Capture the response body from the provider (add temporary logging) — the status alone rarely pinpoints the field at fault

Example fix

// before
const result = await startSiteOAuthConsent(config);

// after
try {
    const result = await startSiteOAuthConsent(config);
} catch (e) {
    if (e instanceof SiteOAuthConsentError && e.status >= 500) {
        return retryableErrorPage(e);
    }
    throw e;
}
Defensive patterns

Strategy: try-catch

Validate before calling

// Pre-flight the consent endpoint before starting the flow
const ping = await fetch(endpoint, { method: 'OPTIONS' });
if (!ping.ok) throw new Error(`Consent endpoint unreachable (${ping.status})`);

Type guard

function isConsentEndpointError(e: unknown): e is SiteOAuthConsentError {
    return e instanceof SiteOAuthConsentError && /OAuth server .* responded with/.test(e.message);
}

Try / catch

try {
    await startSiteOAuthConsent(config);
} catch (e) {
    if (isConsentEndpointError(e)) {
        if (e.status >= 500 || e.status === 429) return retryWithBackoff();
        return renderOAuthConfigError(e);
    }
    throw e;
}

Prevention

When it happens

Trigger: startSiteOAuthConsent or submitSiteOAuthConsentDecision posting to the configured OAuth consent endpoint and receiving 4xx/5xx — wrong client_id/redirect_uri (400/401), expired/invalid consent token (410/401), consent server outage (5xx), or an endpoint URL misconfigured in site OAuth settings.

Common situations: OAuth provider config drift (redirect URI not registered, client credentials rotated), consent tokens expiring while the user sits on the form, the consent service being down or rate-limiting, or a typo in the endpoint URL stored in the integration config.

Related errors


AI-assisted analysis of GitbookIO/gitbook@db67585ee2 (2026-08-28). Data as JSON: /api/errors/45ebd8357b7364a0. Report an issue: GitHub.