Skip to main content
This feature is not available on all Primer accounts. Contact your Customer Success Manager to receive access. See Get started for one-time setup.
This page covers everything between creating the client session and showing the customer a result: capturing the card, deciding where the payment goes, calling your PSP through the proxy, handling 3D Secure, and reporting the outcome. For the simplest end-to-end path, see Your first payment.

Create the client session

Create a client session with the vault’s approval mode and a customerId:

The decision window

Once the customer submits their card, your backend has 15 minutes to either approve the attempt or report an outcome from your own PSP, covering your own checks, the PSP request, and any 3D Secure challenge at your PSP. See Payment attempt states and decision window for what happens if you miss it.

Show the checkout and capture the card

Render the checkout with the client token from the session:
The customer sees your usual Primer Checkout card form; there’s nothing vault-specific in the UI itself. When they submit the form, the SDK tokenizes the card and waits; no payment is created yet. Register a listener for primer:payment-approval-required:
paymentMethodToken carries the token your backend uses next, plus the data it needs to decide where the payment goes. See Decide where the payment goes below.
If the customer refreshes the page while an attempt is still awaiting your decision, the SDK re-emits primer:payment-approval-required for the same paymentAttemptId. Deduplicate on that ID: if you’ve already sent this attempt to your PSP or to Primer, don’t do it again, just call continueFlow().

Decide where the payment goes

Your backend receives, from the approval event, the payment method token and the card’s BIN data: card network, first digits, last four digits, and issuer. If you only use the vault and have no Primer processing connected, the decision is made for you. Take the token and go to the proxy. If you’ve also connected a processor in Primer, you choose per payment, for example by card network, issuer country, or any rule you already run:
A common rule is to route by BIN, for example sending issuer countries you already process well at your own PSP through the proxy, and routing everything else to Primer. Route to Primer: call approve on the attempt. Primer creates the payment and manages the rest of the lifecycle, including 3DS and retries.
approve needs the transactions:authorize scope. If the decision window has expired, approve returns 410 PaymentAttemptExpired and the attempt can no longer be acted on; the customer has to submit a new payment. Route to your own PSP: take paymentMethodToken.token and send your usual PSP request through the proxy, then report the outcome. Stop the payment: call abort if you decide not to proceed at all, for example because your own fraud check failed before you’ve even tried a PSP.
No payment is created and the token is invalidated. Use abort for a decision made before you’ve sent anything to a PSP; if your own PSP processing fails after you’ve already tried it, that’s a fallback to Primer or a reported failure, not an abort.
Only card payments can route to the proxy. Alternative payment methods always go through Primer orchestration, so mixing card and non-card payment methods in the same checkout needs no extra integration work.

Send the request through the proxy

Send the same request you’d normally send to your PSP, but to the proxy, with the card fields replaced by placeholders referencing the token: Replace card fields in the request body with {{<token>.<FIELD>}} placeholders: Placeholders work regardless of how your PSP structures its request body. They’re only resolved in the request body; a placeholder in a header or in the URL path is never substituted and the request fails. A placeholder that’s malformed, references an unknown field, or references a field not available on that token (such as CVV on a saved token) fails the whole request rather than being silently dropped. Stripe example (form-encoded):
Adyen example (JSON):
Most PSP SDKs let you override the base URL and add custom headers, so in many cases you only need to change the base URL your existing PSP client points to and add the two Primer headers; the rest of your integration, including how you build the request body, stays the same. Some SDKs can’t easily add custom headers on every call (notably on OAuth/token calls some SDKs make internally); in that case, call the PSP’s HTTP API directly for the proxied request instead of going through the SDK. See Proxy reference for the full header and error catalogue. The proxy relays your PSP’s response untouched, including its status code. If Primer can’t process the request (for example, an unknown or expired token), the response carries an X-Primer-Proxy-Error header and a problem-details body instead of a PSP response. See Errors and retries.

Handle 3DS at your PSP

3D Secure, when it’s needed, runs at your own PSP. Primer doesn’t run 3DS on a proxied payment.
  • In-page challenge: your PSP’s response tells you a challenge is required; render it in your page using your PSP’s SDK, exactly as you do today. The customer completes it without leaving your checkout.
  • Redirect challenge: the customer is sent to the issuer’s page and back. Your return URL should land them back on your checkout with the same client session.
While the customer is at your PSP or completing a challenge, the Primer checkout has no visibility into what’s happening there, so it shows a loading state while it waits for you to report the outcome. If the customer reloads the page mid-challenge, reload the checkout with the same client token; the SDK reads the current state of the attempt and shows the loading state again, or the result, once you’ve resolved it. This all has to happen inside the 15-minute decision window. See Payment attempt states and decision window for what happens if a challenge runs long.

Report the outcome

Once you have a result from your PSP, resolve the attempt so the checkout can show it:
resolve needs the payment_instrument:write scope. It’s idempotent on paymentAttemptId: a retried call with the same body returns the original response instead of failing, so you can safely retry after a timeout without risking a duplicate. See Errors and retries for the full error catalogue, including what happens if you resolve after the window has expired. Once the outcome is reported, call continueFlow() from your frontend. The checkout listens for the result and shows a success or failure screen:
primer:payment-success and onPaymentSuccess always carry a Primer payment object and don’t fire for a proxied payment. Listen for primer:payment-success-external instead if your integration handles success itself.

Fall back to Primer

If you send a card through the proxy and your own PSP declines or fails, the token is still usable, since the attempt is still open. You can retry with a different PSP of your own, or fall back by calling approve on the same attempt, so Primer processes the same card instead of you reporting FAILED:
Primer doesn’t see your PSP’s response, so it’s your responsibility to only approve once you’ve confirmed your own PSP hasn’t already charged the card. There’s no path in the other direction. Once you approve an attempt for Primer to process, you can’t switch that same attempt to the proxy. If you need your own PSP after an approved payment fails, that’s a new payment attempt, created when the customer retries.