What is a callback URL? OAuth redirects, webhooks and payment returns
A callback URL is an address you give a service so it can send something back to you later: an OAuth redirect URI, a webhook URL or a payment return URL. OAuth redirect URIs must match exactly, and GitHub's codes expire after 10 minutes; Twitch only calls HTTPS on port 443.
A callback URL is an address you register with another service so it can send something back to you later, and the term covers three different things: an OAuth redirect URI where a user's browser lands after signing in, a webhook URL that a provider's server POSTs events to, and a payment return URL where a shopper lands after checkout. Which one a form field means decides the rules; OAuth redirect URIs must match exactly, while webhook URLs must be public HTTPS endpoints that answer fast. Below are the three kinds side by side, the redirect URI rules from the spec and from Google, GitHub and Microsoft, webhook callback requirements, payment return pages, and how to test a webhook callback URL.
Three things called a callback URL
Providers use "callback URL" for three different mechanisms.
An OAuth redirect URI and a payment return URL are opened by a person's browser, so localhost can work during development. A webhook URL is called server to server, so the provider has to reach it over the public internet.
| Kind | Who calls it | What arrives | Example |
|---|---|---|---|
| OAuth redirect URI | The user's browser, sent by the authorization server | code and state in the query string | https://client.example.com/cb?code=...&state=xyz |
| Webhook callback URL | The provider's server, directly | A signed POST with a JSON or form body | Twitch EventSub callback, the Teams outgoing webhook Callback URL |
| Payment return URL | The shopper's browser after checkout | A page load, often with a session ID | Stripe success_url with {CHECKOUT_SESSION_ID} |
OAuth redirect URI: exact match, no fragment
After the user approves access, the authorization server redirects their browser to your redirect URI with a short-lived code and the state value in the query string, and your server exchanges that code for a token. RFC 6749 recommends authorization codes live at most 10 minutes, and GitHub's expire after 10 minutes.
- The URI must be absolute and must not contain a fragment under RFC 6749.
- It must match the registered value by simple string comparison, and RFC 9700 makes exact string matching a requirement except for the port number on
localhostredirect URIs in native apps. - Google OAuth 2.0 requires the
httporhttpsscheme, case and trailing slash to match, rejects wildcards and fragments, and requires HTTPS except forlocalhost. - Microsoft Entra ID redirect URIs are case-sensitive, must start with
httpsexcept forlocalhost, and are limited to 256 characters each. - GitHub matches the callback URL exactly unless wildcard matching is enabled for it, which also allows any subdomain or subdirectory while still requiring the port to match.
When you hit redirect_uri_mismatch, compare the sent redirect_uri and the registered one character by character: scheme, host, port, path, case and trailing slash.
| Who | Matching | HTTPS | Local development |
|---|---|---|---|
| RFC 6749 and RFC 9700 | Exact string match | TLS SHOULD be required for code and token responses | Any port on a loopback IP for native apps (RFC 8252) |
| Exact, including scheme, case and trailing slash; no wildcards | Required, except localhost | http://localhost allowed | |
| Microsoft Entra ID | Case-sensitive; up to 256 characters | Required, except some localhost URIs | http://localhost allowed |
| GitHub | Exact, or subdomains and subdirectories with wildcard matching on | Not covered in GitHub's redirect URL docs | Loopback URLs may use any port |
Webhook callback URL: public, HTTPS, quick to answer
For a webhook, the provider's server calls yours directly, so the webhook callback URL must be reachable from the public internet; a localhost address won't work without a tunnel or relay.
Twitch EventSub names the subscription field callback, requires SSL on port 443, and first sends a webhook_callback_verification message with a challenge your handler must return. Microsoft Teams outgoing webhooks ask for a Callback URL, an HTTPS endpoint that accepts JSON payloads over POST and gives your code 5 seconds to respond. Stripe requires HTTPS for webhook endpoints in live mode (HTTP works for local development), and Twilio calls its webhook requests callbacks too. Every provider verifies and retries differently, but they all expect a fast 2xx response.
Payment return URL: a page, not a confirmation
On a hosted checkout page, Stripe Checkout sends the customer's browser to success_url after payment completes. If you put the literal {CHECKOUT_SESSION_ID} template variable in success_url, Stripe replaces it with the Checkout Session ID so your page can look the session up.
Don't fulfill orders from that landing page. As Stripe notes, the customer isn't guaranteed to reach it, for example if they lose their internet connection right after paying. Fulfill from a webhook handler listening for checkout.session.completed instead; when both that webhook and success_url are set up, Checkout waits up to 10 seconds for your server to answer the webhook before redirecting the customer.
Test a webhook callback URL with a free bin
To inspect what a provider sends, create a free bin at view.webhooks.page, paste its ingest_url wherever the dashboard asks for a webhook or callback URL, trigger an event, and read the request live in your browser: method, path, query string, headers and body. Free bins run on HTTPS over port 443 and answer Twitch EventSub and Slack url_verification challenges automatically, so their verification step passes.
Don't use a bin as an OAuth redirect URI. The authorization code would land in a request log built for webhook testing, not for credentials; for local OAuth work, register a loopback address like http://127.0.0.1 with a port (RFC 8252 requires servers to allow any port on loopback IP redirects) or http://localhost where the provider allows it.
A free bin lasts 48 hours, accepts up to 100 requests per day, and takes bodies up to 100KB. You can claim it with a free Google sign-in before it expires to keep the same URL, or see how to test webhooks locally when you're ready to run your own handler.
# 1. Create a free bin (or open view.webhooks.page)
curl -X POST https://webhooks.sh/v1/bins
# 2. Call it the way a provider calls a callback URL
curl -X POST "https://<bin>.webhooks.page/callback?source=test" \
-H "Content-Type: application/json" \
-d '{"event":"subscription.created","id":"evt_test_1"}'
FAQ
What is a callback URL?
A callback URL is an address you give a service so it can send something back to you later. Depending on the provider, it's a browser redirect URI after OAuth sign-in, a webhook endpoint that receives POST requests, or a return page a shopper lands on after checkout.
Is a callback URL the same as a redirect URI?
In OAuth, yes: GitHub calls the setting a callback URL while the OAuth specs call it a redirect URI. A webhook callback URL is different, because the provider's server calls it directly over the network instead of sending the user's browser there.
Why do I get a redirect_uri_mismatch error?
The redirect_uri sent in the authorization request doesn't match a registered URI in the provider's dashboard. Compare the two strings character by character, including the http or https scheme, host, port, path, letter case and trailing slash.
Can a callback URL be localhost?
For OAuth redirect URIs, often yes: Google and Microsoft Entra ID allow http://localhost, and loopback IP redirects such as http://127.0.0.1 can use any port. For webhooks, no, because the provider's server can't reach your laptop over the public internet without a tunnel or relay.
Does a callback URL need HTTPS?
Usually, yes. Google and Microsoft Entra ID require HTTPS for OAuth redirect URIs except on localhost, Stripe requires HTTPS for webhook endpoints in live mode, and Twitch EventSub only calls HTTPS endpoints on port 443.
How do I test a webhook callback URL?
Create a free bin on webhooks.sh, paste the bin's ingest_url into the provider's webhook or callback field, trigger an event, and watch the request arrive live in the browser. It takes no signup and the bin stays active for 48 hours.
Last updated
Try it in 10 seconds.
Create a free bin at view.webhooks.page — no signup, no install. Point your sender at the URL and watch requests land live. Bins expire after 48 hours.