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.)
The flow
Section titled “The flow”your server Govifi customer----------- ------ --------POST /v1/hosted-sessions → session { hosted_url }redirect customer ───────────────────────────────────────→ opens hosted_url customer pays on hosted page success_url ←───────────────────────────────── redirected backcheckout_session.completed (webhook) → fulfill the order1. Create the session (server-side)
Section titled “1. Create the session (server-side)”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-sessionsAuthorization: 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.
2. Redirect the customer
Section titled “2. Redirect the customer”const session = await createHostedSession(...); // the call aboveres.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.
3. Handle the return
Section titled “3. Handle the return”- Success → the customer is sent to your
success_urlwithsession_idand asigquery 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.
Verifying the sig
Section titled “Verifying the sig”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.
4. Fulfill from the webhook
Section titled “4. Fulfill from the webhook”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.
Redirect vs. embed
Section titled “Redirect vs. embed”| Hosted redirect | Embedded | |
|---|---|---|
| Checkout UI | Govifi-hosted page | <govifi-payment> on your page |
| Your effort | Create session + handle return | Create session + render + wire events |
| PCI scope | SAQ-A (no component) | SAQ-A (card fields in an isolated iframe) |
| Best for | Fastest path, no UI work | Keeping the customer on your site |
Both create the same session and both fulfill from the same webhook — only where the customer pays differs.