---
title: "How to Fix the OAuth redirect_uri Mismatch Error"
canonical: "https://aifitnessapi.com/fix/oauth-redirect-uri-mismatch"
cluster: "Troubleshooting"
primary_query: "oauth redirect_uri mismatch"
last_reviewed: "2026-07-09"
description: "The redirect_uri you send must match your registered callback byte-for-byte. Diff scheme, host, port, path, slash, and encoding to fix the mismatch fast."
publisher: "AIFitnessAPI — independent, not sponsored"
cite_as: "\"How to Fix the OAuth redirect_uri Mismatch Error\", AIFitnessAPI, https://aifitnessapi.com/fix/oauth-redirect-uri-mismatch"
---

# How to Fix the OAuth redirect_uri Mismatch Error

> The OAuth redirect_uri_mismatch error means the redirect_uri your app sends is not byte-for-byte identical to a callback URL registered in the provider's developer console. OAuth servers do an exact string comparison, so http vs https, localhost vs 127.0.0.1, a trailing slash, a port, path case, or encoding differences all break it. Copy the registered value and the value your code actually sends, diff them character by character, and make them match. The same mismatch caught at the token step can surface as invalid_grant instead, so read the error_description.

- Canonical: https://aifitnessapi.com/fix/oauth-redirect-uri-mismatch
- Last reviewed: 2026-07-09
- Publisher: AIFitnessAPI (https://aifitnessapi.com) — independent, not sponsored
- Cite as: "How to Fix the OAuth redirect_uri Mismatch Error", AIFitnessAPI, https://aifitnessapi.com/fix/oauth-redirect-uri-mismatch

---

You clicked through the OAuth consent screen (or you POSTed the authorization code) and the provider threw back `redirect_uri_mismatch` — or, at the token step, an unhelpful `invalid_grant`. The cause is almost always the same: the `redirect_uri` your code sent is not **byte-for-byte identical** to a callback URL registered in the provider's developer console. Fix it by copying the registered value and the sent value side by side and diffing them character for character — scheme, host, port, path, trailing slash, query, and encoding all count.

OAuth servers do an **exact string comparison** on the redirect URI, not a semantic one. `https://app.example.com/callback` and `https://app.example.com/callback/` are two different strings to the server, even though a browser treats them the same. That single rule explains nearly every mismatch on this page.

## Most likely causes (ranked)

From most to least common in fitness/health API integrations (Fitbit, Strava, Oura, WHOOP, Garmin):

1. **`http` vs `https`.** You registered `https://...` but your local dev server sends `http://localhost...` (or the reverse). The schemes must match exactly.
2. **`localhost` vs `127.0.0.1`.** These are **not** equal to the string matcher, even though they resolve to the same machine. Pick one and register that exact host. The same applies to `example.com` vs `www.example.com`.
3. **Trailing slash.** `/callback` and `/callback/` are different. This is the sneakiest one because frameworks and proxies sometimes add or strip the slash for you.
4. **Port present vs absent.** `http://localhost:3000/callback` is not `http://localhost/callback`, and `https://app.example.com:443/callback` is not `https://app.example.com/callback` (443 is implicit for https). Register the port exactly as your code emits it.
5. **Path differences (including case).** `/callback` vs `/oauth/callback` vs `/Callback`. Paths are case-sensitive to the matcher.
6. **URL-encoding differences.** `%2F` vs `/`, or uppercase vs lowercase in a percent-encoded segment. Encode the `redirect_uri` query parameter once and consistently; do not double-encode it.
7. **The two steps disagree.** The `redirect_uri` sent to the **authorize** step and the one sent to the **token** step must be the same string. If they differ, you may pass the authorize step and then get rejected at the token exchange — where it can surface as `invalid_grant` rather than `redirect_uri_mismatch`.

> **Why you sometimes see `invalid_grant` instead.** Per RFC 6749, the token endpoint returns `invalid_grant` when the grant "does not match the redirection URI used in the authorization request." So a redirect mismatch caught at the **authorize** step reads as `redirect_uri_mismatch`, but the same mismatch caught at the **token** step can read as `400 invalid_grant`. Always log the `error_description` — providers put the specific reason there.

## How to fix it

### Step 1: Print the exact URI your code sends

Before anything else, log the literal `redirect_uri` string your app puts on the wire, at both the authorize step and the token step. Do not read it from a config variable you assume is correct — log the actual outgoing value.

```bash
# Whatever your code builds, echo the exact string it sends:
echo "$REDIRECT_URI"
# e.g. http://localhost:3000/callback
```

### Step 2: Copy the registered URI from the developer console

Open the provider's app settings (Fitbit `dev.fitbit.com`, Strava's API settings, Oura, WHOOP, or the Garmin developer portal) and copy the registered **Callback / Redirect URI** verbatim. Paste both values into a plain-text editor, one above the other.

### Step 3: Diff them character by character

Walk the two strings left to right and check each component. A quick way to catch invisible differences (trailing slash, whitespace, encoding) is to compare them programmatically rather than by eye:

```bash
# Prints "MATCH" only if the two strings are byte-for-byte identical.
REGISTERED="https://app.example.com/callback"
SENT="https://app.example.com/callback/"
[ "$REGISTERED" = "$SENT" ] && echo "MATCH" || echo "MISMATCH"
# MISMATCH  <- the trailing slash on $SENT is the bug
```

Use this checklist as you compare:

- scheme — `http` vs `https`
- host — `localhost` vs `127.0.0.1`, apex vs `www`
- port — present vs absent, explicit `:443` vs implicit
- path — spelling and case, `/callback` vs `/oauth/callback`
- trailing slash — `/callback` vs `/callback/`
- query string — any extra or reordered params
- percent-encoding — `%2F` vs `/`, and the case of encoded characters

### Step 4: Make the two OAuth steps use the identical string

Keep the `redirect_uri` in one constant and pass that same constant to both the authorize URL and the token exchange. If they diverge, the token step rejects you.

```bash
# Authorization-code exchange (Strava shown). The redirect_uri here MUST equal
# the one used to build the authorize URL earlier — same string, character for character.
curl -X POST https://www.strava.com/oauth/token \
  -d client_id=$CID -d client_secret=$SECRET \
  -d code=$AUTH_CODE \
  -d grant_type=authorization_code \
  -d redirect_uri=https://app.example.com/callback
# A 400 invalid_grant here is often the redirect_uri differing from the authorize step
# (or a reused/expired code) — read error_description to disambiguate.
```

### Step 5: Register every environment explicitly

Local dev, staging, and production each need their own registered callback (for example `http://localhost:3000/callback`, `https://staging.example.com/callback`, and `https://app.example.com/callback`). Add each one to the console rather than trying to make one entry cover all environments.

### Step 6: Never append dynamic data to the redirect URI

Do not tack per-request query parameters onto `redirect_uri` — an unregistered or reordered query string will fail the exact-match check. Put per-request data in the `state` parameter instead, which is designed for exactly this and also protects against CSRF.

## Still stuck? Quick triage

Run this short checklist:

- Log the **literal** sent `redirect_uri` at both steps and diff each against the console value with a string-equality check, not your eyes.
- Confirm scheme, host, port, path, trailing slash, query, and encoding all match — this covers essentially every case.
- Confirm the authorize step and token step send the **same** string.
- If you only see `invalid_grant` (not `redirect_uri_mismatch`), read `error_description`; it may be a reused or expired authorization code rather than the URI — codes are single-use and short-lived.
- If your app sits behind a proxy or framework that rewrites the path, verify what actually leaves your process, since the proxy may add or strip a trailing slash after your code builds the URL.

Once the callback matches, you're back on the happy path. For the full authorization flow per provider, see the [integration guides](/integrate) — for example [Fitbit](/integrate/fitbit-api), [Strava](/integrate/strava-api), or [Garmin](/integrate/garmin-api). And if your calls start returning `401` after auth succeeds, that's a different problem — head to [Fix a fitness API 401 Unauthorized error](/fix/fitness-api-401-unauthorized).

## FAQ

### Why do localhost and 127.0.0.1 count as a mismatch?

OAuth servers compare the redirect_uri as an exact string, not by what it resolves to. localhost and 127.0.0.1 are different strings even though they point to the same machine, so you must register and send whichever one your code actually uses.

[Permalink](https://aifitnessapi.com/fix/oauth-redirect-uri-mismatch#faq-1)

### Does a trailing slash really matter?

Yes. /callback and /callback/ are different strings to the matcher, so one will fail if the other is registered. Frameworks and proxies sometimes add or strip the slash after your code builds the URL, so verify what actually leaves your process.

[Permalink](https://aifitnessapi.com/fix/oauth-redirect-uri-mismatch#faq-2)

### Why do I get invalid_grant instead of redirect_uri_mismatch?

A redirect mismatch caught at the authorize step reads as redirect_uri_mismatch, but the same mismatch caught at the token step can read as 400 invalid_grant because the grant no longer matches the redirect used in the authorization request. Read the error_description field, since invalid_grant can also mean a reused or expired authorization code.

[Permalink](https://aifitnessapi.com/fix/oauth-redirect-uri-mismatch#faq-3)

### Do the authorize and token steps need the same redirect_uri?

Yes. The redirect_uri sent when you build the authorize URL and the one sent when you exchange the code must be the identical string. If they differ you may pass the authorize step and then be rejected at the token exchange.

[Permalink](https://aifitnessapi.com/fix/oauth-redirect-uri-mismatch#faq-4)

### How do I support local, staging, and production callbacks?

Register each environment's exact callback URL separately in the developer console, for example an http localhost URL for dev and https URLs for staging and production. Do not try to make a single entry cover multiple environments.

[Permalink](https://aifitnessapi.com/fix/oauth-redirect-uri-mismatch#faq-5)
