Install
require() it on Node 20.19+ / 22.12+, or use dynamic import() on
older runtimes.
Quickstart
Construct a client with a secret key and create a checkout session:Webhooks
Verify inbound deliveries withwebhooks.unwrap(). It checks the signature
and replay window, then returns a typed event. Pass the raw request body
exactly as received; a body that’s been parsed and re-serialized by framework
middleware will not verify:
WebhookVerificationError (bad signature,
missing headers, stale timestamp) or WebhookParseError (authenticated body
that isn’t a webhook envelope). Respond 400 in both cases so the sender’s
retry logic kicks in. Never 200 a delivery you didn’t process.
Fulfill from the webhook, not from the browser redirect. The buyer’s
success_url redirect proves they returned to your site; it doesn’t prove
the payment completed. In success_url/return handling, check the session
with checkout.sessions.retrieve, and do the actual order
fulfillment when checkout.session.completed arrives.
The signing secret is shown once, in the response from
webhookEndpoints.create. Store it immediately; it isn’t retrievable
afterward.
Typed imports
Every resource shape is exported alongside the client:Error handling
Every failed API call throwsIonicApiError. Catch that one class and branch
on statusCode and the error body; it carries the parsed API error envelope
plus a requestId to quote when contacting support:
CardError, InvalidRequestError, AuthenticationError,
PermissionError, NotFoundError, ConflictError, RateLimitError and
ApiError, all extending IonicApiError. There is exactly one of each in the
package: the per-resource names the API reference uses (BadRequestError,
UnauthorizedError, …) are aliases of these, so IonicApi.checkout.BadRequestError
and InvalidRequestError are the same class and instanceof cannot pick a
wrong one.
Every error carries statusCode, a requestId worth quoting to support, and
detail: the typed error body (code, message, decline_code,
decline_type, retryable, doc_url), or undefined when the response had
no parsable envelope, as with some 502 responses. errorDetail(err) reads the same
value from an unknown.
The specific class comes from the statuses an endpoint documents.
InvalidRequestError, AuthenticationError, PermissionError,
RateLimitError and ApiError are documented on every endpoint, so those
always arrive as their class.
CardError, NotFoundError and ConflictError are documented only where the
endpoint can produce them: a list call cannot 404, and a customer lookup cannot
raise a card decline. Anything undocumented arrives as IonicApiError with the
correct statusCode and detail, so it is still catchable.
Reliability
-
Idempotency keys are automatic. Every mutating request (POST, PUT,
PATCH, DELETE) carries an
Idempotency-Key; the SDK generates one per call when you don’t supply your own, so the automatic retries below replay the original operation instead of repeating it (a second charge, a second refund). To also make your own application-level retries safe, pass the same"Idempotency-Key"field with the same request body: the original response is replayed instead of the operation running twice. Replay is performed by the API. API key creation is the one write it does not cover, because those keys can be created by a platform rather than a single merchant. A retried key creation may therefore produce a second key; list your keys to see whether the first request completed before creating another one. -
Retries. Requests that fail with
408,429,502,503, or504are retried automatically with backoff. A bare500is never retried: on a mutating endpoint it can mean the operation already committed. -
Auto-pagination. List calls on
paymentIntents,charges,refunds,customers,invoices,subscriptions,creditNotes,paymentMethods,setupIntents,catalog.products,catalog.prices,paymentLinks, andcheckout.sessions,webhookEndpoints, andapiKeysreturn aPagethat exposeshasNextPage()/getNextPage()and is directlyfor await-able. Iteration advances withstarting_aftercursors, so rows created while you page are never skipped or repeated:If the first request carried anoffset, continuation requests drop it; iteration advances by cursor alone. API-key and webhook-endpoint lists preserve their legacy full-result behavior whenlimitis omitted; the returnedPageis terminal in that case.
Security
- The secret key (
sk_v1_test_…/sk_v1_live_…) authenticates every request in this SDK. Keep it server-side only, read from an environment variable. Never ship it to a browser or commit it to source control. - The webhook signing secret (
whsec_…) is returned once, at endpoint creation. If you lose it, callwebhookEndpoints.rotateSecret()for a new one; the previous secret is revoked immediately, and it isn’t recoverable otherwise. - Test keys (
_test_) returnlivemode: falseobjects and never move real money. Live keys (_live_) do. Keep the two separate in your environment configuration and never charge a live key from a test script.

