A callback URL is an address you give to another service so it can call you back later, when something has finished or happened. Instead of you polling for a result, the service sends a request to your callback URL. Depending on the context, that request is a browser redirect (OAuth login, payment checkout) or a server-to-server HTTP call (webhooks, async job results).
https://app.example.com/auth/callback?code=4/0AbCD...&state=xyz123
That's an OAuth callback: the provider sent the user's browser back to your app with an authorization code.
Where callback URLs show up
| Context | Who calls it | What arrives |
|---|---|---|
| OAuth / OpenID Connect login | The user's browser, via a redirect | code and state in the query string |
| Payment checkout return | The user's browser, via a redirect | An order or session ID |
| Async API job (transcription, video, SMS status) | The provider's server | A POST with the job result |
| Webhooks | The provider's server | A POST with an event payload |
The name is borrowed from programming, where a callback is a function you pass to code that will call it later. A callback URL is the same idea across the network.
Callback URL vs webhook URL
A webhook URL is a specific kind of callback URL:
- Registration. A webhook URL is registered once, in a dashboard or by API, and receives every matching event until you remove it. Callback URLs are often passed per request: Twilio's
StatusCallbackparameter, or theredirect_uriin each OAuth authorization request. - Transport. Webhooks are always server-to-server. OAuth and checkout callbacks go through the user's browser.
- Content. A webhook carries an event payload and usually a signature. An OAuth callback carries a short-lived code that is only useful together with your client secret.
In casual use the two terms overlap. Some providers label the webhook setting "callback URL", so check the docs for whether the provider will redirect a browser or send a request from its servers.
How an OAuth callback URL works
- Your app sends the user to the provider with
redirect_uri=https://app.example.com/auth/callbackand a randomstate. - The user logs in and approves access.
- The provider redirects the browser to your callback URL with
?code=...&state=.... - Your server checks that
statematches what it issued, then exchanges thecodefor tokens in a back-channel request.
app.get('/auth/callback', async (req, res) => {
if (req.query.state !== req.session.oauthState) return res.sendStatus(400)
const tokens = await exchangeCodeForTokens(req.query.code)
req.session.user = await fetchProfile(tokens.access_token)
res.redirect('/dashboard')
})
Common mistakes
redirect_uri_mismatch. OAuth providers compare the callback URL exactly: scheme, host, port, path and often the trailing slash.http://localhost:3000/callbackandhttp://localhost:3000/callback/are different URLs.- Skipping the
statecheck. Without it, an attacker can complete a login flow in someone else's session (login CSRF). - Using
localhostfor a server-to-server callback. A browser redirect tolocalhost:3000works because the user's own browser follows it. A job-complete callback from a provider's servers can't reach your machine. Use a public URL that forwards to it, through a tunnel or webhook forwarding. - Trusting the callback blindly. For server-to-server callbacks, verify the signature or fetch the result from the provider's API before acting on it.
- Slow handlers. Server-to-server callbacks behave like webhooks: return
2xxquickly or the provider may time out and retry. See webhook retries.
Testing callbacks
For browser-redirect callbacks during development, register a http://localhost redirect URI if the provider allows it. For server-to-server callbacks, point the provider at a Webhook Bin URL to see exactly what it sends, then run relay forward --bucket my-app http://localhost:3000/callbacks to deliver real callbacks to your local code through a permanent public URL.
