Skip to content

Hosted redirect flow

The simplest way to take a payment: create a session on your server, redirect the customer to the Govifi-hosted checkout page, and fulfill from the webhook. The component never loads on your site, so your PCI scope stays SAQ-A and you write no checkout UI. (Prefer the payment on your own page? See Embed the component.)

your server Govifi customer
----------- ------ --------
POST /v1/hosted-sessions → session { hosted_url }
redirect customer ───────────────────────────────────────→ opens hosted_url
customer pays on hosted page
success_url ←───────────────────────────────── redirected back
checkout_session.completed (webhook) → fulfill the order

Authenticated with your Cognito admin token + the X-Payment-Account-Uid header — never from the browser. Include the success_url / cancel_url the customer returns to. See Create a hosted session for the full request.

POST /v1/hosted-sessions
Authorization: Bearer <admin token>
X-Payment-Account-Uid: <account uid>
Content-Type: application/json
{ "amount": 14227, "currency": "USD",
"success_url": "https://yoursite.example/receipt?session_id={CHECKOUT_SESSION_ID}",
"cancel_url": "https://yoursite.example/billing" }

The response includes hosted_url.

const session = await createHostedSession(...); // the call above
res.redirect(session.hosted_url);

Use hosted_url exactly as returned — the session credential travels in its URL fragment (#cs=…), so the hosted page authenticates with no extra wiring. The fragment isn’t sent to servers or in Referer, and is dropped when the page redirects onward. The customer lands on the Govifi-hosted page (checkout.govifi.io in production, checkout.govifi.dev in sandbox) and pays there.

  • Success → the customer is sent to your success_url with session_id and a sig query parameter. The literal {CHECKOUT_SESSION_ID} token is substituted.
  • Cancel / back → they’re sent to your cancel_url.

Treat the redirect as UX only. Browsers close and redirects get lost — so do not fulfill the order here.

sig is HMAC-SHA256(yourWebhookSigningSecret, "{session_id}.{transaction_id}"), hex-encoded — the same signature the confirm result and your recorded transaction carry. It proves the redirect corresponds to a real completed payment. You get transaction_id from the checkout_session.completed webhook (the source of truth), so verify on your server after the webhook has recorded the payment:

import { createHmac, timingSafeEqual } from 'node:crypto';
// transactionId comes from the checkout_session.completed webhook you've recorded for this session.
function verifyReturn(sessionId, transactionId, sig, webhookSigningSecret) {
const expected = createHmac('sha256', webhookSigningSecret)
.update(`${sessionId}.${transactionId}`)
.digest('hex');
const a = Buffer.from(sig);
const b = Buffer.from(expected);
return a.length === b.length && timingSafeEqual(a, b);
}

If the webhook hasn’t arrived yet, show a “processing” state rather than treating the page as verified — the redirect alone carries only session_id + sig, never the transaction_id.

Fulfillment is driven by the checkout_session.completed webhook (verify the signature, idempotent on webhook_id). This is the source of truth, not the success_url round-trip. See Webhooks.

Hosted redirectEmbedded
Checkout UIGovifi-hosted page<govifi-payment> on your page
Your effortCreate session + handle returnCreate session + render + wire events
PCI scopeSAQ-A (no component)SAQ-A (card fields in an isolated iframe)
Best forFastest path, no UI workKeeping the customer on your site

Both create the same session and both fulfill from the same webhook — only where the customer pays differs.