Skip to main content
The server-side TypeScript SDK. It holds your secret key, so it runs on your backend only, never in a browser. Method signatures, required fields, and webhook event shapes match the API reference exactly.

Install

Requires Node 18 or later. This is an ES module package; CommonJS projects can 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 with webhooks.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:
An unverifiable delivery throws 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 throws IonicApiError. 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:
The hierarchy is 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, or 504 are retried automatically with backoff. A bare 500 is 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, and checkout.sessions, webhookEndpoints, and apiKeys return a Page that exposes hasNextPage() / getNextPage() and is directly for await-able. Iteration advances with starting_after cursors, so rows created while you page are never skipped or repeated:
    If the first request carried an offset, continuation requests drop it; iteration advances by cursor alone. API-key and webhook-endpoint lists preserve their legacy full-result behavior when limit is omitted; the returned Page is 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, call webhookEndpoints.rotateSecret() for a new one; the previous secret is revoked immediately, and it isn’t recoverable otherwise.
  • Test keys (_test_) return livemode: false objects 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.