Skip to content

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.