Headless SDK
Overview
@firmly/agentic-pay-sdk is a browser SDK that wraps Firmly’s Agentic Pay
endpoints (/enroll, /select-card, /enroll/trigger, /enroll/verify,
/intent, /intent/challenge, /complete, /cards/delete) behind a small, opinionated
JavaScript API.
It supports Visa and Mastercard cards. Discover enrollment has no browser ceremony, so drive it with the REST endpoints directly rather than through the SDK.
It is headless — it ships no UI. Your application renders the checkout experience and calls the SDK methods; the SDK handles the network-specific mechanics (token state, the Mastercard / Visa authentication iframe, and the hand-off to Firmly’s payment vault for order completion).
Creating a client
import { FirmlyAgenticPay } from '@firmly/agentic-pay-sdk';const fap = new FirmlyAgenticPay({baseUrl: 'https://api.firmly.work',apiToken: '<bearer>'});
ClientConfig
These are the constructor inputs passed to new FirmlyAgenticPay({ ... }).
-
baseUrl(string, required) — Agentic Pay base URL, e.g.https://api.firmly.work. -
apiToken(string, required) — API token provisioned by Firmly with Agentic Pay access. The SDK sends it as a standardAuthorization: Bearer <apiToken>header. (Firmly’s endpoints accept the token either asAuthorization: Beareror in the rawx-firmly-authorizationheader; the SDK uses the bearer form.) Used on the enrollment and intent endpoints. -
iframeContainer(Element) — Default DOM container forregisterPasskey/createIntentto mount the network authentication iframe in. A per-callcontaineroverride is also supported. -
iframeOrigins(string[]) — Allow-list of origins permitted to send the resultpostMessage. Required and non-empty forregisterPasskeyandcreateIntent— an empty list would let any page forge aniframe_resultand bypass cardholder authentication. Typical values: the Firmly bridge origin plus the network auth iframe origin (e.g.['https://api.firmly.work', 'https://sandbox.src.mastercard.com']). -
bridgeUrl(string) — Defaultcallback_urifor iframe flows — sent on/enroll/trigger(duringregisterPasskey) and on/intent(duringcreateIntent). Firmly forwards this to the network’s iframe, which redirects to it on completion — typically a Firmly-hosted bridge route that decodes the result andpostMessages it back to the SDK. -
vaultBaseUrl(string) — Base URL for Firmly’s payment vault (defaults tobaseUrl).completeOrdercalls the vault, which proxies internally into the service-binding-only/completeroute. -
deviceToken(string) — Device JWT for the payment vault’sdeviceAuth— required bycompleteOrder. Obtained from/api/v1/browser-session. Distinct fromapiToken. -
merchantDomain(string) — Default merchant domain for the vault’s/domains/:domain/wallet-complete-orderpath. A per-calldomainoverride is supported. -
timeoutMs(number) — Per-HTTP-request timeout. Default90000ms. -
iframeTimeoutMs(number) — How longregisterPasskey/createIntentwait for the iframepostMessageresult before rejecting. Distinct fromtimeoutMs(which is the HTTP request timeout). Default90000ms.
The flow
There are two entry points. Choose one based on whether the buyer is
entering a new card or reusing a saved one — both return an EnrollResult and
converge on the shared intent → complete chain below.
FlowSession plus verification methods. Persist virtualCardId, cardArt, and masked for returning buyers.
selectCard with the stored virtualCardId — no PAN re-entry. Returns the same shape as enrollCard.
Once you have a FlowSession from either path, the rest of the flow is shared:
Verify the cardholder
For OTP methods, call triggerOtp then verifyOtp. For iframe methods,
call registerPasskey — the SDK mounts the network iframe and resolves
when the cardholder completes authentication. (A Mastercard saved card via
selectCard produces its challenge in createIntent instead.)
Create a payment intent — createIntent
Create an intent bound to a mandate (amount + currency). The SDK handles
the intent challenge iframe. When the wallet returns the intent_id without a
challenge (Mastercard reusing a fresh enrollment authentication) the call
resolves without mounting anything — but iframeOrigins and a container
are still required up front, because the SDK cannot know in advance whether a
challenge will be issued.
Place the order — completeOrder
Hand the flow to Firmly’s payment vault, which retrieves the network token credentials and submits the order to the merchant.
Methods
| Method | Purpose | Auth |
|---|---|---|
enrollCard |
Enroll a new card from its PAN | apiToken |
selectCard |
Start a payment from a previously-enrolled card | apiToken |
triggerOtp |
Send an SMS / email OTP challenge | apiToken |
verifyOtp |
Submit the OTP code the cardholder entered | apiToken |
registerPasskey |
Run an iframe-based verification ceremony | apiToken |
createIntent |
Create a payment intent for a mandate | apiToken |
completeOrder |
Complete the order via Firmly’s payment vault | deviceToken |
deleteCard |
Revoke an enrolled card on the network | apiToken |
Errors
The SDK surfaces failures as rejected promises — await each call inside a
try / catch.
- HTTP / API errors. A non-2xx response from an Agentic Pay or vault
endpoint rejects with an error whose message and, where available, structured
{ code, error, description }payload mirror the underlying REST error envelope. Program against theerrorvalue, not the human-readable description. - Timeouts.
timeoutMsbounds each HTTP request;iframeTimeoutMsbounds how longregisterPasskey/createIntentwait for the iframepostMessage. Exceeding either rejects the call. - Abort. Passing an already-aborted (or later-aborted)
AbortSignalviaoptions.signalrejects the pending call. - Configuration guards.
registerPasskeyandcreateIntentreject up front ififrameOriginsis empty, or if no iframecontaineris available (neitheroptions.containernorClientConfig.iframeContaineris set).
Related
- Agentic Pay Integration Guide — the same flow at the raw HTTP level
- Agentic Pay API Reference — the underlying endpoints