> ## Documentation Index
> Fetch the complete documentation index at: https://docs.payrails.com/llms.txt
> Use this file to discover all available pages before exploring further.

> ## Agent Instructions
> Use the Payrails Web SDK v6 for new browser integrations. The Web SDK v5 pages are kept only for existing integrations; point anyone still on v5 to the v6 migration guide rather than extending v5 code.
> Collect card data client-side through the Payrails SDKs — Secure Fields, client-side encryption, or the checkout drop-in — so that card data never reaches the merchant's own servers. Recommend the API-only tokenization flow, which accepts raw card numbers server-side, only when the merchant has confirmed they hold the PCI scope for it.
> Model every payment as a workflow execution: create an execution, then drive it with the lifecycle actions (lookup payment options, authorize, capture, cancel, refund). Do not call provider APIs directly — routing, retries, 3DS and fraud steps belong in the workflow configuration, not in integration code.
> Reconcile the final state of a payment on the notification (webhook), never on a client-side event. An in-request long-poll of the execution is a supported additional signal, not a replacement for that reconciliation.
> Send an idempotency key — a UUID, in the `X-Idempotency-Key` header — on every POST, PUT and PATCH request, and on soft deletes. GET requests need none, and hard deletes cannot be idempotent.
> Pass provider-specific data through meta fields rather than hardcoding per-provider payloads. Payrails translates meta fields into each provider's own format.
> Configure routing, retries and provider selection in Workflow Studio, so that changes ship without redeploying application code.

# Event API Reference

> Reference for the web SDK event API: every instance event and element event, the payload each one carries, and when it fires.

The SDK exposes events through the typed `.on(name, handler)` API at two levels:

1. **Instance events** — `payrails.on(name, handler)`. Session-level and
   in-flight payment-attempt events (`success`, `failed`, `pending`,
   `buttonClicked`, `requestStart`, `actionRequired`, `sessionExpired`,
   `deliveryAddressChanged`).
2. **Element events** — `element.on(name, handler)`, where `element` is the
   object returned by `payrails.cardForm()`, `payrails.paymentButton()`,
   `payrails.googlePayButton()`, and so on. Element events describe one rendered
   element (`change`, `focus`, `blur`, `ready`, `saveInstrumentCheckboxChanged`,
   `preferredSchemeChanged`, `billingAddressChanged`, `validate`,
   `stateChanged`, `paymentOptionSelected`).

`.on()` returns an unsubscribe function. Handlers may be async; the SDK awaits
each handler before proceeding. A handler that throws is logged and isolated
(other handlers still run). `Payrails.init` resets all subscriptions.

```ts theme={null}
const off = payrails.on("success", (event) => {
  console.log(event.paymentMethodCode, event.action);
});
// later:
off(); // or payrails.off('success', handler)
```

## Init-time callback (the one exception)

The only bag callback that still exists is `onClientInitialized`, passed inside
`PayrailsClientOptions.events` on `Payrails.init`. It has no `.on()` form
because it fires during construction, before a `.on(...)` listener could
register.

```ts theme={null}
const payrails = await Payrails.init(initResponse, {
  events: {
    onClientInitialized: (execution) => useExecution(execution),
  },
});
```

Fires once when the `Payrails` instance is constructed, and again after a
successful session refresh (see `sessionExpired`). Its argument is the execution
response object (`WorkflowExecutionResponse`, e.g. `execution.id`).

## Instance events — `payrails.on(name, handler)`

Every payment-attempt payload carries `PaymentAttemptContext`:

| Field | Type | Description |
| - | - | - |
| `executionId` | `string` | The workflow execution id of the attempt. |
| `paymentMethodCode` | `string` | The payment method of the attempt. |
| `action` | `'AUTHORIZE' \| 'TOKENIZE'` | Whether it's a payment or an instrument save. |

One listener fires for **any** element's event in the session — filter with
`event.paymentMethodCode` / `event.action` if you need per-method behaviour.

### `success`

Fires when authorization (or tokenization) completes successfully.

Payload: `{ …ctx, data? }`. `data` carries the tokenization result on a
`TOKENIZE` success (notably for Google Pay / Apple Pay) and is absent for flows
that return nothing (e.g. `AUTHORIZE`).

### `failed`

Fires when the attempt fails or is canceled.

Payload: `{ …ctx, data?: { code?, message? } }` where `code` is an
`AuthorizationFailureReasons` value (`VALIDATION_FAILED`, `AUTHORIZATION_ERROR`,
`AUTHENTICATION_ERROR`, `UNKNOWN_ERROR`, `USER_CANCELLED`).

### `pending`

Fires when the payment ends in a pending state (e.g. the result is not final yet
after a redirect or challenge).

Payload: `{ …ctx, data? }`.

### `requestStart` (cancelable)

Fires immediately before the authorize/tokenize request is sent.

Payload: `{ …ctx, preventDefault() }`. Call `preventDefault()` to abort — the
SDK then fires `failed` with `code: 'VALIDATION_FAILED'`.

### `buttonClicked` (cancelable)

Fires when the shopper clicks the payment button, before anything is submitted.

Payload: `{ …ctx, bin?, preventDefault() }`. For card payments `bin` carries the
current BIN. Call `preventDefault()` to abort — the SDK then fires `failed` with
`code: 'VALIDATION_FAILED'`.

### `sessionExpired`

Fires when the SDK detects the client session has expired, or when a payment
attempt fails because the session/vault token is no longer valid.

Payload: `void`. Handlers **may** return a fresh `InitOptions`
(`{ version, data }`); the SDK awaits all handlers, re-inits from the first
non-null result, and then re-runs the init `onClientInitialized` callback.

```ts theme={null}
payrails.on("sessionExpired", async () => fetchNewInitResponse());
```

### `deliveryAddressChanged` (cancelable)

Fires when the shopper changes the shipping address inside an express-checkout
sheet (PayPal, Apple Pay).

Payload: `{ …ctx, deliveryAddress?, preventDefault() }`. Call `preventDefault()`
to reject the address (the Apple Pay sheet shows an error and prompts for
another address; PayPal cancels the change).

* Apple Pay exposes only redacted fields (`city`, `postalCode`, `state`,
  `country.code`) before the shopper authorizes; the full address is available
  on `success`. Apple Pay requires a response within \~30 seconds — keep this
  handler fast.
* On PayPal, the payload shape is
  `{ orderID?, paymentID?, paymentToken?, shippingAddress?: { city, countryCode, postalCode, state } }`.

### `actionRequired` (cancelable)

Fires when a payment attempt needs a shopper-facing action and the SDK is about
to perform its default behavior — navigating to a redirect URL or presenting the
3DS challenge (popup or redirect).

Payload (`ActionRequiredEvent`):

| Field | Type | Description |
| - | - | - |
| `url` | `string` | The URL the shopper must be taken to. |
| `kind` | `'genericRedirect' \| '3ds'` | What the action is (`ACTION_REQUIRED_KIND` values). |
| `executionId` | `string` | The workflow execution id of the payment attempt. |
| `paymentMethodCode` | `string` | The payment method of the attempt. |
| `preventDefault()` | `() => void` | Call to take over: the SDK skips its default redirect/popup and you handle `url` yourself. |

```ts theme={null}
payrails.on("actionRequired", (event) => {
  if (event.kind === "genericRedirect") {
    event.preventDefault();
    myRouter.openInModal(event.url);
  }
});
```

## Element events — `element.on(name, handler)`

Element events are declared on the object returned by an element factory
(`cardForm`, `paymentButton`, `googlePayButton`, `applePayButton`,
`paypalButton`, `dropin`, `dynamicElement`, …). A name lives on exactly one
element type; using it on the wrong element is a compile error.

### `change`

Fires on every card-form input change.

Payload: `{ isValid, cardNetwork, bin?, billingAddress?, cardSchemes? }`. On a
standalone secure field (collect element) the payload is
`{ isValid, isEmpty, network, bin?, cardSchemes? }` — note `network`, not
`cardNetwork`.

### `focus` / `blur`

Fires when a card field gains / loses focus. Focusing the card form also clears
a `cardList` selection.

Payload: `void`.

### `ready`

Fires when all secure fields have mounted and the form is interactive.

Payload: `void`.

### `saveInstrumentCheckboxChanged`

Fires when the shopper toggles the "save instrument" checkbox.

Payload: `{ checked: boolean }`.

### `preferredSchemeChanged`

Fires when the detected/selected card scheme changes for a co-branded card.

Payload: `{ preferredScheme: string, cardSchemes: CardScheme[] }`.

### `billingAddressChanged`

Fires when the shopper edits the billing address form.

Payload: `{ isValid: boolean, billingAddress? }`.

### `validate`

Fires after the card form is validated on click, before the payment request.

Payload: `{ isValid, error?, fieldErrors? }`. `fieldErrors` maps fields to their
validation errors.

### `stateChanged`

Fires when a payment button switches between enabled and disabled (e.g. as
card-form validity changes).

Payload: `{ state: 'enabled' | 'disabled' }`.

### Wallet availability (not an event)

Wallet availability is exposed as `readonly isAvailable: Promise<boolean>` on
`googlePayButton`, `applePayButton`, and `paypalButton` — not as an event,
because it is a capability check that settles once. The promise never rejects;
it resolves to `false` on any check-side failure.

### `paymentOptionSelected`

Fires when the shopper selects a payment option in the drop-in.

Payload: `{ paymentMethod?, instrument? }`. `paymentMethod` is the selected
method's configuration; `instrument` is set when a stored instrument was
selected.

## Usage

```ts theme={null}
import { Payrails } from "@payrails/web-sdk";

const payrails = await Payrails.init(initResponse, {
  events: { onClientInitialized: (execution) => useExecution(execution) },
});

const cardForm = payrails.cardForm();
cardForm.on("change", (e) => render(e.isValid));
cardForm.on("ready", () => show());

payrails.on("buttonClicked", async (e) => {
  if (!(await ok())) e.preventDefault();
});
payrails.on("success", (e) => console.log(e.paymentMethodCode));
payrails.on("failed", (e) => console.log(e.data?.code));
payrails.on("sessionExpired", async () => fetchNewInitResponse());
```

<br />


## Related topics

- [Apple Pay Express](/docs/orchestration/payment-methods/apple-pay-express.md)
- [API References](/docs/orchestration/checkout-sdks/react-native/api-references.md)
- [Get an API Log event by ID](/reference/getapilogevent.md)
