Handle the result

The customer's browser brings them back, so nothing plain in that URL can be trusted. The broker appends a signed token instead.

The return

return_to
https://yourapp.com/billing/callback?session_id=7f3a9c1e-…&pay_token=eyJhbGciOiJIUzI1NiJ9…

pay_token is a JWS (HS256) signed with your secret key. Verify it on your backend, never in the browser. It is not called token so that auth libraries that read a token from the URL (Clowk, Devise and the like) leave it alone:

Ruby
claims = JWT.decode(params[:pay_token], ENV["PAY_SECRET_KEY"], true,
  algorithm: "HS256", iss: "pay", verify_iss: true,
  sub: params[:session_id], verify_sub: true).first

claims["status"]   # "paid", "failed", "expired", …
claims["amount"]   # "49.9", with "currency", "method" and "customer"
Node
import jwt from "jsonwebtoken"

const claims = jwt.verify(req.query.pay_token, process.env.PAY_SECRET_KEY, {
  algorithms: ["HS256"], issuer: "pay", subject: req.query.session_id,
})

Rules

  1. 1Pin HS256 and reject anything else, including none.
  2. 2The token expires 15 minutes after the redirect and can be replayed until then: grant what was bought once per session_id.
  3. 3It proves the status at redirect time. Refunds and late payments arrive by webhook (session.paid, session.refunded).
  4. 4Rather not verify tokens? Ignore it and call GET /api/v1/sessions/:session_id with your key.

Are you sure?

Keyboard shortcuts

⌘ KSearch and navigate⌘ JOpen and close the Workbenchg hHomeg pPaymentsg cCustomersg sSubscriptionsg aAppsg gGateways?This listEscClose