1. Overview
The Revolv3 Web SDK lets a merchant collect credit card details on their own checkout page without the raw card number, expiry, or CVV ever touching the merchant’s JavaScript. Card inputs render inside a cross-origin iframe hosted by Revolv3; the merchant’s page only ever sees tokenized results and field-level state (empty/complete/valid/error), never the underlying values. There are two integration paths:- Collecting a fresh card —
tokenize,sale,authorize. These are handled inside the hosted iframe, because raw card data lives there. - Charging a previously saved card —
saleWithPaymentMethod,authorizeWithPaymentMethod. These call the Revolv3 API directly from the merchant’s page, because the only input is an id — there’s no raw card data to protect.
<script src> tag, as a self-contained bundle that doesn’t require a build step on the merchant’s side. It exposes a single global, Revolv3WebSdk, matching the library name baked into the build:
- ESM build
- UMD build
2. Quick Start
Step 1 — Mint a session and payment key (merchant backend)
Before any client-side code runs, the merchant’s own backend — authenticated the same way it already authenticates to the rest of the Revolv3 API — must call two Revolv3 endpoints. The demo app’s small local backend wraps these two calls behind its own/session and /ott/{sessionId} routes; a real merchant backend calls Revolv3 directly.
1. Register a session for this checkout attempt:
amount/currency you register here is what actually gets charged later — the browser-side sale/authorize call can never override it, so a tampered client can’t change the price.
2. Mint a one-time payment key, immediately before each submit attempt:
sale, not once up front). This endpoint is rate-limited (10 requests per 60 seconds per calling IP, by default).
If a payment attempt fails, the session moves to a Failed status and can’t be used for another attempt as-is. Call POST /api/web-sdk/auth/order-session/{sessionId}/reset first (this only works while the session is Failed or Expired), then mint a new payment key and retry.
Step 2 — Add a mount point
create() replaces this element’s children with a single iframe containing all four card inputs, so it should be an otherwise-empty container sized by your page’s CSS.
Step 3 — Initialize and mount
create() rejects after 15 seconds if the iframe never finishes its setup handshake, so a genuinely broken network fails fast rather than hanging forever.
Step 4 — Listen for state
Step 5 — Submit
Step 6 — Verify server-side
Treat the resolved result as informational only for UI purposes. There is no dedicated status-check endpoint under the web-sdk API for your backend to poll bysessionId — instead, configure a webhook per Revolv3’s webhook docs and treat that as the authoritative source of truth for the outcome, rather than the client-side promise result.
The two events relevant to a payment made through this SDK:
InvoiceStatusChanged— fires on transitions of the overall invoice status (the sameinvoiceStatusvalues from §6).InvoiceAttemptStatusChanged— fires when a specific payment attempt’s status changes (the sameinvoiceAttemptStatusvalues from §6).
InvoiceId, InvoiceStatus, Subtotal, Total, NetworkTransactionId, EventDateTime, EventType, RevolvMerchantId, etc.) signed with HMAC-SHA256 in an x-revolv3-signature header — verify that signature before trusting the payload, respond 200 immediately and process asynchronously, and handle the delivery idempotently since webhooks can be redelivered.
3. Configuration Reference
The object passed to init()
That’s the whole config object now — there is no merchant identifier and no origin URL to supply.
environment alone resolves both the hosted-fields iframe URL and the API origin internally; an unrecognized value throws rather than silently defaulting to one or the other. Both are baked into the SDK build itself (“both environments ship in every build, so a single published bundle serves sandbox and prod” per the source comment), not configured per-integration.
The object passed to create()
4. Hosted Fields
Field types
One iframe renders all four inputs together — there is no per-field mount API.selector targets a single container; the hosted-fields app fills it with a form containing:
Layout is responsive: expiry and CVV sit side-by-side above 420px width and stack to one column below it, automatically.
Styling
Every visual property is a CSS custom property scoped to the iframe document — merchants set values, never selectors, by passing plain string keys in thestyles object. Unrecognized property names and values that fail a basic safety check (see §9) are dropped silently, not applied and not thrown back to the merchant page.
create() completes its handshake; there’s no separate “update styles” call — remount (destroy() then create() again) to change styling after the fact.
Custom Fonts
If your brand font isn’t a system font, load it into the hosted fields viafonts and then reference it from --revolv3-font-family:
{ family, src }. Constraints, enforced silently (an entry that fails any of these is just dropped — no error is thrown or surfaced to your code, only a console.warn inside the iframe):
- Up to 8 fonts per
create()call. srcmust be an absolute HTTPS URL ending in.woff2,.woff,.ttf, or.otf.- The font’s host must be either
fonts.gstatic.comor the same origin as your merchant page. A self-hosted font file must therefore be served from your own domain, and that response needs anAccess-Control-Allow-Originheader permitting the hosted-fields iframe’s origin, or the browser will block the cross-origin font load even though the URL passed the SDK’s own check. - Requires the browser to support the
FontFaceAPI — on browsers that don’t,fontsis silently ignored rather than breaking the rest ofcreate().
5. Events
Subscribe withhostedFields.on(eventName, callback), unsubscribe with hostedFields.off(eventName, callback).
A
state object looks like this:
submitStart/submitCompletereport nothing about success or failure — they exist purely to drive UI like a global spinner around the call. Whether the operation succeeded must come from the resolved value or thrown error of thetokenize/sale/authorizecall itself, not from these events. In particular, a 200-level response can still represent a declined payment (seeinvoiceAttemptStatusin §6) —submitCompletefires the same way either way.
Errors are intentionally suppressed while a field is still plausibly mid-entry:state.errorstays absent until a field is either complete or has been blurred. Achangelistener wired up to show inline errors won’t show anything for a half-typed card number, by design.
6. Payment Operations
These three operations collect a fresh card and are handled inside the hosted iframe. Each now requires the merchant to supply its own reference id in the request — these are not optional, and a missing one rejects the call before anything is sent over the wire (forsale/authorize; see the validation note under §8 for tokenize’s different handling).
request also accepts the optional billingAddress object from §8 on all three. sale additionally accepts an optional merchantPaymentMethodRefId.
All three return a promise. request is optional and, if provided, is a plain object with one recognized property:
invoiceStatus and invoiceAttemptStatus (on tokenize/sale, not currently on authorize) are two different things — invoiceStatus is the invoice's overall lifecycle state, invoiceAttemptStatus is the outcome of this specific charge attempt:
Concurrency & timeouts
Only one payment operation can be in flight at a time per SDK instance. Callingtokenize/sale/authorize/saleWithPaymentMethod/authorizeWithPaymentMethod while a previous call hasn’t resolved yet rejects immediately with "<operation> already in progress" — it does not queue. Disable your submit button between submitStart and submitComplete to avoid this in normal use. This is also enforced server-side, independently — see §9.
Each call auto-rejects with "<operation> timed out." if no response arrives within 30 seconds.
7. Saved Payment Methods
saleWithPaymentMethod and authorizeWithPaymentMethod charge or authorize an already-tokenized card by payment method id. Because there’s no raw card data involved, these calls go directly from the merchant’s page to the Revolv3 API — they don’t involve the iframe at all, which is a different trust model from §6 worth calling out explicitly to readers coming from that section.
submitStart/submitComplete/single-in-flight/timeout behavior as §6.
8. Validation & Error Handling
Field-level errors
An error’scode is stable and safe to branch on; message is an overridable English default, not a contract.
Validation rules applied live inside the iframe:
If a merchant calls
sale/authorize (or their “with payment method” counterparts) with incomplete or invalid fields, the SDK short-circuits before any network call with a 422-style error ("Please complete all required fields."), and forces every field’s error state to be revealed/broadcast — so change listeners will see errors for fields the user never touched.
Request-level validation
Beforesale, authorize, saleWithPaymentMethod, or authorizeWithPaymentMethod sends anything, the SDK checks the request object’s own shape — including the required reference id fields from §6/§7 — and rejects with a validation error (not the usual {message, statusCode, details} shape from below) if something’s missing or too long. tokenize does not run this pre-flight check — an invalid or missing merchantPaymentMethodRefId passed to tokenize is sent as-is and only rejected (if at all) by the server.
billingAddress (optional, on all four card-collecting requests)
Error shape
Every rejected payment call throws a plain error-like object:9. Security Model
- Raw PAN, expiry, and CVV are read, formatted, and validated entirely inside the hosted-fields iframe. The merchant page never receives these values in any event payload or result — only tokenized/masked results (payment method id, last-4, BIN).
- The iframe is loaded with a locked-down sandbox (scripts, forms, and same-origin allowed; nothing else) and sends no referrer.
- Every cross-window message is checked on both sides: the merchant page only accepts messages from the iframe origin resolved for the chosen
environment, whoseevent.sourceis the exact iframe window the SDK itself created (not just a matching origin string) and whose session id matches the current session; the iframe applies the equivalent origin + session checks against the calling page before acting on anything. - On mount, the iframe calls a Revolv3 endpoint to verify that the merchant/session/origin triple is authorized before revealing the card form at all — a failed check hides the fields and rejects
create()’s promise with the iframe’s error message, instead of silently resolving. - Style values are filtered twice: only recognized CSS custom property names are considered, and values are rejected if empty, over 256 characters, or contain
url(,@import,expression(,javascript:, or a backtick — a basic CSS-injection guard. Rejected values are dropped silently rather than surfaced as an error your code can catch. - Custom fonts (§4) go through their own allowlist — HTTPS only, a fixed set of font file extensions, and a host restricted to Google Fonts or the merchant’s own page origin — independent of the style-value guard above.
- The charge amount is locked in server-side, not client-supplied.
amount/currencyare set once when the merchant backend registers the order session (§2, step 1) and are read from that stored session on every payment call — nothing the browser sends can change what’s charged. - The “one payment operation at a time” behavior described in §6 is also enforced server-side, independently of the SDK’s own in-memory guard: each order session can only move from its
Pendingstatus toProcessingonce. A second concurrent attempt against the same session is rejected by the server even if the client-side guard were somehow bypassed. A failed attempt leaves the session in aFailedstatus that must be explicitly reset (§2, step 1) before another attempt is possible. - The payment key is single-use — the server invalidates it the moment a payment call using it is accepted, independent of the client-side “already in progress” check.
- The domain-verification call the iframe makes on mount sends only
{ sessionId, origin }— no merchant/client identifier at all. That’s consistent with the backend: the owning merchant is resolved entirely from the order session record (established under the merchant backend’s own authenticated identity in §2, step 1).

