JS / TS
Early access
@shotpay/node — view on npm.
npm install @shotpay/node
Requires Node 18 or newer. Ships ESM and CJS builds with bundled type definitions, and has no runtime dependencies.
import { ShotPayClient } from '@shotpay/node';
const client = new ShotPayClient({
clientId: process.env.SHOTPAY_CLIENT_ID!,
apiKey: process.env.SHOTPAY_API_KEY!,
// baseUrl defaults to the sandbox — set it to the live host in production.
});
Every method
| Method | API reference | Scope |
|---|---|---|
checkoutSessions.create |
Open a checkout session | checkout:write |
checkoutSessions.retrieve |
Verify a session from your server | checkout:read |
contracts.list |
List contracts | contracts:read |
contracts.retrieve |
Retrieve a contract | contracts:read |
contracts.update |
Update a contract | contracts:write |
contracts.cancel |
Cancel a contract | contracts:write |
verifyWebhookSignature |
— | none |
Checkout sessions
checkoutSessions.create
Uses the Open a checkout session API.
Opens a session and returns it, including the checkout_url to redirect to.
create(params: CreateCheckoutSessionParams, idempotencyKey?: string): Promise<CheckoutSession>
Pass idempotencyKey to make a retry safe — replaying the same key with the
same params returns the original session rather than opening a second one.
const session = await client.checkoutSessions.create(
{
order_reference: order.number,
total_amount: order.totalCents, // integer cents — 49999 is $499.99
return_url: 'https://store.example/checkout/complete',
cancel_url: 'https://store.example/cart',
},
order.shotpayIdempotencyKey,
);
redirect(session.checkout_url);
checkoutSessions.retrieve
Uses the Verify a session from your server API.
Reads a session your server opened, without waiting on a webhook.
retrieve(id: string): Promise<CheckoutSession>
This is the call that settles an order. Put it in one function and call that function from both of the places that learn a checkout resolved:
export async function fulfil(sessionId: string) {
const session = await client.checkoutSessions.retrieve(sessionId);
if (session.status !== 'completed') return;
const order = await orders.findByReference(session.order_reference);
// The webhook and the returning customer race routinely.
if (!order.paid) await orders.markPaid(order.id);
}
Contracts
The agreements a completed checkout becomes, addressed by the reference
webhooks carry — never a row id.
contracts.list
Uses the List contracts API.
One page of your agreements, newest first.
list(params?: ListContractsParams): Promise<Paginated<Contract>>
| Parameter | Notes |
|---|---|
status |
Optional. active, completed, cancelled or defaulted. |
page |
Optional. Defaults to the first page. |
Page size is fixed by the API — there is no per_page. The links.next URL is
absolute and this client will not follow it for you, so step page instead:
let page = await client.contracts.list({ status: 'active' });
while (page.meta.current_page < page.meta.last_page) {
for (const contract of page.data) {
// contract.reference, contract.remaining_amount, contract.installments
}
page = await client.contracts.list({
status: 'active',
page: page.meta.current_page + 1,
});
}
contracts.retrieve
Uses the Retrieve a contract API.
One agreement with its full installment schedule.
retrieve(reference: string): Promise<Contract>
const contract = await client.contracts.retrieve('LAY-XK2M9QPR7V');
contract.remaining_amount; // integer cents still to collect
contract.installments; // sequence, amount, due_date, status, paid_at
A reference belonging to another merchant answers 404 rather than 403, and
so does one of your own from the other mode.
contracts.update
Uses the Update a contract API.
Corrects bookkeeping. Money, status and the customer's identity are not writable — anything not named below never reaches the record.
update(reference: string, params: UpdateContractParams): Promise<Contract>
| Parameter | Notes |
|---|---|
order_reference |
Optional. Your own identifier, up to 100 characters. |
metadata |
Optional. String keys and values. Replaces the stored map rather than merging; null clears it. Max 50 keys, keys 40 chars, values 500. |
await client.contracts.update('LAY-XK2M9QPR7V', {
order_reference: 'SO-2026-0042',
metadata: { crm_id: '77' },
});
contracts.cancel
Uses the Cancel a contract API.
Ends an active agreement. No further installments are collected; paid installments stay paid.
cancel(reference: string): Promise<Contract>
Cancelling an already-cancelled agreement returns it as it stands, so a retry
is safe. One that already completed or defaulted raises a 422. A
contract.cancelled webhook goes to your subscribed endpoints.
await client.contracts.cancel('LAY-XK2M9QPR7V');
Webhooks
verifyWebhookSignature
Confirms a delivery came from ShotPay and has not been altered. Node only — the endpoint secret it needs is a server-side secret exactly as an API key is.
verifyWebhookSignature(
payload: string,
secret: string,
header: string,
toleranceSeconds?: number, // defaults to 300
): boolean
import { verifyWebhookSignature } from '@shotpay/node';
if (!verifyWebhookSignature(rawBody, endpointSecret, signatureHeader)) {
throw new Error('Invalid webhook signature');
}
Verify against the raw bytes, not re-serialised JSON — see Signatures.
Errors
Every non-2xx raises ShotPayApiError, carrying the HTTP status and the
decoded response body. Which statuses are worth retrying is on
Error Handling.
import { ShotPayApiError } from '@shotpay/node';
try {
await client.contracts.cancel(reference);
} catch (error) {
if (error instanceof ShotPayApiError && error.status === 422) {
// Already completed or defaulted — nothing left to cancel.
}
}
Deprecated
client.createCheckoutSession() and client.retrieveCheckoutSession() still
work and call the same endpoints, but are removed in 0.3.0. Use
checkoutSessions.create and
checkoutSessions.retrieve.