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
- 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
- Verify the consent endpoint URL in your site OAuth configuration is current and reachable from the deployment
- Re-initiate the consent flow so a fresh consent token is used if the old one expired
- 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
- Keep OAuth client_id/secret/redirect URIs in sync with the provider after any rotation
- Log the upstream response body (not just status) when consent calls fail
- Retry only 5xx/429; persistent 4xx means configuration drift
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.