Programmatic API
For anything the two zero-JS entry points can't express — opening the checkout from your own event handler, deferring origin resolution, or building a fully custom trigger element — call createKlappayOne() directly. ui/klappay-button.ts and ui/auto-wire.ts are themselves thin callers into this exact function; there's no separate logic path hiding behind the markup-based entry points.
import { createKlappayOne } from '@klappay/one'
const klappayOne = createKlappayOne({
chargeId: 'ch_123',
origin: 'https://klap.one',
locale: 'en',
mode: 'iframe',
onReady: () => console.log('checkout loaded'),
onSuccess: (result) => {
// result: PaymentResult — see below. UX signal only, see /protocol.
},
onError: (error) => {
// error: KlappayOneError — see /errors.
},
onCancel: () => console.log('payer closed the checkout'),
})
document.querySelector('#pay-button')!.addEventListener('click', () => {
klappayOne.open()
})KlappayOneConfig
interface KlappayOneConfig {
chargeId: string
origin: string
locale?: string
mode?: 'iframe' | 'popup'
onReady?: () => void
onSuccess?: (result: PaymentResult) => void
onError?: (error: KlappayOneError) => void
onCancel?: (reason: 'user' | 'closed') => void
onReconnecting?: (state: 'started' | 'recovered' | 'failed') => void
}| Field | Description |
|---|---|
chargeId | Required. The Charge this checkout is for — created ahead of time on your backend. |
origin | Required. Which Klap origin to open — no default, since sandbox and production point at different hosts. |
locale | Forwarded to the checkout. |
mode | 'iframe' | 'popup' — forces a mode instead of the iframe default. |
onReady | Fires once the popup/iframe signals it has loaded (klappay:ready). Useful for hiding a loading spinner over the trigger button. |
onSuccess | Fires with a PaymentResult once the payer completes payment. Not proof of payment — see Protocol & security. |
onError | Fires with a KlappayOneError — see Errors for every code. |
onCancel | Fires when the payer closes the popup/iframe without completing payment, by any means — an explicit Cancel button inside the checkout, the browser's native close button, alt-F4, swiping away a mobile popup. |
onReconnecting | Fires when the payer returns from backgrounding the tab/app (e.g. switching to their wallet app to approve, then coming back) and one-id has to check whether its WalletConnect relay connection survived — 'started' while it retries, then 'recovered' or 'failed'. Purely informational, safe to ignore; never fires after a terminal onSuccess/onError/onCancel. See Errors. |
PaymentResult
interface PaymentResult {
txHash: string
walletAddress: string
network: string
amount: string
confirmedAt: string
walletVerified?: boolean
}Every field here is public on-chain data by the time this fires — a transaction hash, a wallet address, which network, how much, and when it confirmed. None of it is a secret, and none of it is a substitute for your backend's own webhook-driven fulfillment.
walletVerified is true when Klap One tied the payment to the payer's own wallet, and false when Klap Core confirmed the charge as paid by this transaction but none of the on-chain evidence names that wallet (a wallet paying through a relayer and a swap, for example). Either way the charge is paid, so onSuccess fires and the payer sees "Payment received" — you don't need to handle false differently unless you want to. An older one-id may omit the field; treat a missing value like true for UX purposes.
open()
interface KlappayOne {
open: () => void
}Calling open() again while a checkout from a previous open() call is still in flight opens a second, independent popup/iframe — createKlappayOne() itself does no de-duplication; that's what ui/klappay-button.ts's disabled guard and ui/auto-wire.ts's data-klappay-one-busy guard exist for on the two markup-based entry points. If you're calling open() from your own click handler, guard it the same way:
let busy = false
button.addEventListener('click', () => {
if (busy) return
busy = true
klappayOne.open()
})
// then clear `busy` in onSuccess/onError/onCancelEach call to open() generates its own requestId (crypto.randomUUID()) internally — that's what lets the bridge tell which open() call a given postMessage response belongs to, so more than one createKlappayOne() instance can safely coexist on the same page (e.g. one button per line item in a cart). See Protocol & security for the full wire format.
configure() / getGlobalConfig()
import { configure, getGlobalConfig } from '@klappay/one'
configure({ origin: 'https://klap.one', locale: 'en' })
getGlobalConfig() // -> { origin: 'https://klap.one', locale: 'en' }Sets the page-wide default origin/locale that <klappay-button> and data-klappay-one fall back to when they don't carry their own origin/locale attribute. createKlappayOne() itself never reads this — the programmatic API always requires origin explicitly, since it has no natural attribute to fall back to and no excuse to make a required field implicit.
See it running
The vanilla example in Examples calls createKlappayOne() exactly as shown above, wired to a real backend route that creates the charge first — clone it and run it instead of copy-pasting the snippets on this page.