Verify magic link

PUBLIC

Completes the magic link login flow. Submits the challenge token from the magic
link email and returns Cognito JWT tokens on success.

Events fired

EventTriggerDescription
authorization.CodeRedeemedsyncFired when the token is successfully verified and exchanged.
authorization.CodeRedeemFailedsyncFired when verification fails (invalid or already used token).
Recent Requests
Log in to see full request history
TimeStatusUser Agent
Retrieving recent requests…
LoadingLoading…
📘

Eventsauthorization.CodeRedeemed, authorization.CodeRedeemFailed

Why this endpoint exists

The customer clicked the link in their e-mail and arrived at your callback_url with a challenge token attached. This endpoint completes the login: it verifies the token and returns a session.

Possession of the token proves control of the mailbox, which is the whole basis of the magic-link flow. That is also why the token is single-use and short-lived.

Call it from the front-end, so the tokens and cookies land in the customer's browser.

Request

curl -X POST https://api.acc.funtrips.io/v2/auth/link/verify \
  -H "Content-Type: application/json" \
  -d '{
    "token": "d3f8a1c…",
    "session": "AYABeH…"
  }'
FieldTypeRequiredNotes
tokenstringYesChallenge token from the magic link URL
sessionstringNoOpaque session handle from the create step, when your flow carried it through

Read both off your callback URL's query string. URL-decode once — double-decoding is a common cause of a 404 on a token that is actually fine.

Response

200 OK, plus cookies.

{
  "data": {
    "access_token": "eyJraWQ…",
    "id_token": "eyJraWQ…",
    "refresh_token": "eyJjdHki…",
    "token_type": "Bearer",
    "expires_in": 3600,
    "expires_at": "2026-09-03T15:35:00Z"
  },
  "meta": { "correlation_id": "…" }
}
FieldUse
access_tokenBearer token for authenticated calls
id_tokenIdentity token. Present here, unlike on code exchange
refresh_tokenExchange at refresh before expiry
expires_in / expires_atLifetime, relative and absolute

The response also sets access_token and refresh_token as HttpOnly; Secure; SameSite=None cookies. A browser client can rely on those and never handle the tokens in JavaScript.

Errors

StatustypeMeaningWhat to do
400token missingCheck your URL parsing
404authorization-code-not-foundToken unknown or expiredOffer to send a new link
409authorization-code-already-redeemedLink already usedSee below
401authorization-challenge-failedVerification failedOffer to send a new link
500Unexpected fault. Retryable

409 is usually not a problem

Mail clients and security scanners pre-fetch links. By the time the customer clicks, the token may already be spent — and the session it minted went to a scanner, not to them.

There is no way to distinguish that from a genuine second click, so handle 409 gracefully: check whether you already hold a valid session (GET /me) and continue if so; otherwise offer to send a fresh link with a plain explanation. Never show a raw error.

Expect a background rate of this. It is a property of e-mail, not of the integration.

const res = await fetch(`${BASE}/auth/link/verify`, {
  method:      'POST',
  headers:     { 'Content-Type': 'application/json' },
  credentials: 'include',
  body:        JSON.stringify({ token, session }),
});

if (res.status === 409 || res.status === 404) {
  const me = await fetch(`${BASE}/me`, { credentials: 'include' });
  if (me.ok) return goToStorefront();     // already signed in
  return offerNewLink();                  // ask for a fresh e-mail
}

Strip the token from the URL after verifying (history.replaceState) so a refresh does not retry a spent token.

Notes

  • id_token is returned here but not on code exchange. If your client depends on it, do not assume it is present on both paths.
  • authorization.CodeRedeemed on success; authorization.CodeRedeemFailed on an invalid or already-used token — which is where a pre-fetching mail scanner shows up in the event stream.
Body Params

Completes the magic link login flow by submitting the challenge token.

Headers
Responses

Language
URL
LoadingLoading…
Response
Click Try It! to start a request and see the response here! Or choose an example:
application/json
application/problem+json