Troubleshooting
Fixes for the most common Campus One integration errors — sign-in, the App API, webhooks, and custom subdomains.
A quick reference for the errors developers hit most often. Each entry lists the symptom, the usual cause, and the fix.
Sign-in & OIDC
redirect_uri mismatch / "invalid redirect"
Symptom: Campus One refuses to redirect back, or you see an invalid_request / redirect URI error on the authorize step.
Cause: The redirect_uri your app sends must exactly match one of the URLs registered on your app (scheme, host, port, and path all count — http vs https, a trailing slash, or localhost vs 127.0.0.1 will all fail).
Fix: Open your app → Sign-in tab → Redirect URLs and add the exact callback your framework uses, e.g. http://localhost:3000/api/auth/callback/campus-one. Redirect URL changes take effect immediately.
error=invalid_scope
Symptom: The authorize request is rejected with invalid_scope.
Cause: You requested a scope the admin hasn't enabled for your app, or a scope that doesn't exist. Valid scopes are openid, profile, email, offline_access, academic, calendar, notifications, roles, events.
Fix: Request only scopes your app is granted (see Permissions). calendar and events are off by default — ask an admin to enable the matching perm* flag.
PKCE errors (code_verifier / code_challenge)
Symptom: The token exchange fails with a PKCE or invalid_grant error.
Cause: PKCE is mandatory. Either you didn't send a code_challenge on the authorize request, or the code_verifier at token exchange doesn't match the original challenge (often because the verifier wasn't persisted across the redirect).
Fix: Use code_challenge_method=S256, store the code_verifier in a secure cookie/session before redirecting, and send it back unchanged at the token step. Most OIDC libraries do this automatically — prefer one over a hand-rolled flow.
Missing claims (no student_id, role, etc.)
Symptom: The ID token is valid but a claim you expected is absent.
Cause: Claims are gated by both scope and the app's permission flag. student_id/level/faculty_id/department_id need the academic scope and permAcademic. The roles array and custom_roles only appear with the roles scope. Some fields are simply empty for that user (e.g. a staff member has no student_id).
Fix: Confirm the scope is requested and granted, then re-consent. Note the top-level role claim is always present — you don't need the roles scope just to read the primary role. See OIDC claims.
Consent screen shows every time
Symptom: The student re-approves on each sign-in.
Cause: Consent is re-prompted when the requested scope set changes. For internal first-party apps you may not want any prompt.
Fix: Keep your scope set stable. For trusted internal apps, toggle Trusted app in the dashboard to skip the consent screen entirely.
Session drops locally / cookies rejected
Symptom: Login succeeds but the session doesn't stick in local dev.
Cause: Secure-cookie or cross-subdomain settings fighting http://localhost.
Fix: Use http://localhost (not a custom hostname) for local testing — the auth server automatically relaxes secure/cross-subdomain cookie rules there. See localhost testing.
App API (notifications & events)
401 Unauthorized
Cause: The Bearer token is missing, malformed, expired (access tokens live ~1 hour), revoked (the user signed out or disconnected your app), or not bound to a user.
Fix: Send Authorization: Bearer <access_token>. If expired, refresh it (requires the offline_access scope). If the user disconnected, restart the sign-in flow. A 401 body is the gateway shape { "error": "..." } (not the { code, status, message } shape used by 400/403). See App API auth.
403 Forbidden
Cause: The token is valid but lacks the required scope, or your app's matching permission flag is off. Notifications need the notifications scope + permNotifications; events need the events scope + permEvents.
Fix: Ensure the user authorized the scope and an admin enabled the flag (permEvents is off by default). The message field tells you which one is missing.
400 Bad Request
Cause: Payload validation failed — a missing required field, a string over its limit (title 128 / body 512 for notifications; title 200 / description 1000 for events), an invalid notification type, or a startsAt that isn't ISO 8601.
Fix: Check the field limits in the Notifications and Events references, or the live spec.
Duplicate notifications/events after a retry
Cause: A network blip made you resend a write that actually succeeded.
Fix: Send an Idempotency-Key header (a UUID or stable composite). A repeated key returns the original record instead of creating a duplicate; keys are cached per app for 24 hours.
Webhooks
Signature verification fails
Symptom: Your HMAC check never matches X-Campus-One-Signature.
Cause: Almost always a body-parsing issue — you hashed the parsed/re-serialized JSON instead of the exact raw bytes Campus One signed.
Fix: Verify against the raw request body, before any JSON middleware runs. In Express, mount express.raw({ type: "application/json" }) on the webhook route ahead of express.json(). Use the published verifier:
import { verifyWebhook } from "@campus-one/auth/webhooks";See the webhooks guide.
Events arrive late, out of order, or not at all
Cause: Delivery is fire-and-forget today — a 5-second timeout, no automatic retries. Deliveries aren't ordered.
Fix: Respond 2xx fast and process asynchronously. Deduplicate on the X-Campus-One-Delivery id and resolve conflicts with occurredAt. Don't rely on webhooks as your only source of truth — reconcile via sign-in / the REST API. Failed deliveries increment your app's errorCount on the dashboard.
Users aren't signed out across apps
Cause: You're not honouring session.signed_out.
Fix: Subscribe to session.signed_out and clear the local session immediately. Even without it, access ends when the current token expires (Campus One revokes tokens on logout, so refresh fails). See single logout.
Custom subdomains & DNS
Subdomain stuck "pending" / not going live
Cause: DNS hasn't propagated yet, or the CNAME doesn't resolve to your registered target. The propagation agent checks every ~10 minutes.
Fix: Confirm your host points the subdomain at the CNAME target you registered, then wait for a check cycle. You can verify resolution yourself:
curl -H "accept: application/dns-json" \
"https://cloudflare-dns.com/dns-query?name=yourapp.campusone.com.ng&type=CNAME"When it resolves, the app's homepage URL is promoted automatically and you get an in-app notification + email. See Subdomains.
Can't edit subdomain or homepage URL
Cause: Once a custom subdomain is verified and active, the subdomain, CNAME target, and homepage URL inputs lock to protect routing integrity.
Fix: Contact a Campus One admin to migrate a verified subdomain or CNAME target.
Still stuck?
- The interactive API reference is the authoritative source for App API schemas and status codes.
- Feed the docs to an AI assistant via AI Integration and ask it to diagnose against the source of truth.
- Contact the Campus One platform team or open a ticket through the admin console.