In an environment where the current flow is enabled, Facto opens the card page you registered. The cardholder signs in, chooses cards, and approves the change on that page. Your backend then reads the current card list and submits the complete list the cardholder confirmed.

Use the Engine host and server credential issued for the same enabled environment as the Card Program. Credentials never move between environments.

Prerequisites

Facto configuresYour team builds
An approved Program Connection and a server credentialA cardholder sign-in, card selection, and approval page
The exact connect_page_url that Facto may openA backend that verifies the connection hint and calls the Engine API

Keep the server credential out of the browser. Use provider-only card IDs plus the network, card type, and last four digits for display. PAN and CVV are rejected. There is no Server SDK for the card-list operations.

Connection sequence

Connect handshake

Who calls whom, in order, from the cardholder choosing your program to the window sealing.

CardholderFactoapp + EngineConnect pageyour frontendProgram backendyour server1 Choose your program2 Open a 15-minute window409 when binding admission is closed3 Open connect_page_urlconnect_hint + connect_intent_id ride the URL fragment4 Hand off hint + intent idthe only browser-to-server hop5 GET /.well-known/facto-card-program-connect/jwks.jsonpublic and unauthenticated6 Ed25519 JWK set7 verifyFactoCardProgramConnectHint()Facto signs, you verify8 Select cards, approveyour page owns consent — keep the approval_ref9 GET /connect-intents/{id}/associationsfour-header HMAC10 current cards + portfolio_revisionalt[cardholder approved]11 PUT /connect-intents/{id}/associationsIdempotency-Key required · send the complete desired set12 200 applied | no_change[cardholder cancelled]13 POST /connect-intents/{id}/cancelbody is the hint aloneA portfolio_revision that moved underyou returns 412 stale_portfolio andleaves the window open: re-readassociations and resubmit.14 Show the connected cards
Your backend performs every authenticated call. The browser only carries the hint from the URL fragment to your server, which is why the server credential never has to reach the page.

Create the connect intent and open the Program page

The cardholder chooses the program in Facto and continues. Facto first opens a blank popup from that click, then creates the connection request and sends the popup to connect_page_url. Ask the cardholder to allow popups; the current interface reports a blocked popup and requires a retry. The URL fragment carries connect_hint and connect_intent_id, so neither enters server logs or the Referer header. Read them, then remove the fragment from the address bar.

Verify the connect hint

Pass the hint from your page to your backend and verify it with /.well-known/facto-card-program-connect/jwks.json. The verifyFactoCardProgramConnectHint helper from @facto/connect/server performs this check. This is the only SDK helper for the current flow.

Explain which cards will be connected and what will be shared. Let the cardholder add or remove cards, then require explicit approval. Keep an auditable approval_ref for that decision.

Read current associations

The response includes the current associations and portfolio_revision.

GET/api/card-program-connect/connect-intents/{connect_intent_id}/associations

Submit the final desired card set

Send the complete post-update set of cards that should remain connected. An empty list removes every card this user connected to this program. Include the connection hint, approval_ref, authorized_changes_digest, and portfolio_revision in the JSON body, plus Idempotency-Key in the header.

PUT/api/card-program-connect/connect-intents/{connect_intent_id}/associations

Facto stores the approval reference and digest as the Program's audit record. It checks their format, but does not recompute the digest or independently verify your approval page.

Apply the transaction and track status

Facto saves all card changes in one transaction or saves none. applied means the list changed; no_change means it already matched. If the cardholder cancels, report it explicitly:

POST/api/card-program-connect/connect-intents/{connect_intent_id}/cancel

Send { "connect_hint": "eyJ..." } as the JSON body. An empty body is invalid.

Facto polls the connection status. The status returned by the Engine is authoritative.

If Facto pauses new card connections, a request that adds cards returns 423 binding_admission_closed. A request that only removes cards still works. Closing the popup without reporting cancellation leaves the request pending until it expires; a retry needs a new request.

PaymentRoute configuration

Each card retained in the final set has an active card binding. Payment eligibility also requires the cardholder to associate a PaymentRoute in Facto. The Card Program cannot read PaymentRoute or payment_readiness. If the connection result still requires a funding source, the cardholder returns to Cards, opens the card, and selects a pipeline with active status. Continue with card bindings and consent and PaymentRoute and authorization.