Skip to content

Payment API

Send payments to Statsy from your own server when your billing does not come from a supported provider webhook.

If you bill through something Statsy has no webhook for — your own billing code, an invoicing tool, a marketplace, a bank transfer you reconcile nightly — send the payment yourself. It lands in exactly the same place as a webhook payment.

The endpoint

text
POST https://statsy.co/api/v1/sites/<site>/payments

<site> is the site's public id or its ws_ key. Authenticate with a bearer token created under Site settings › API tokens: a site token (st_) limited to one site is the right choice here. Account tokens (sta_) work too.

bash
curl https://statsy.co/api/v1/sites/ws_XXXXXXXX/payments \
  -H "Authorization: Bearer st_XXXXXXXXXXXX" \
  -H "Content-Type: application/json" \
  -d '{
    "external_id": "inv_20260915_881",
    "amount": 49.00,
    "currency": "USD",
    "kind": "one_time",
    "email": "buyer@example.com",
    "occurred_at": "2026-09-15T10:04:00Z"
  }'

Body fields

FieldRequiredNotes
external_idYesYour own unique id for this payment, up to 255 characters. This is what makes the call idempotent.
amountYesIn major units49.00 means forty-nine dollars, not forty-nine cents.
currencyYesA three-letter code. Lower case is accepted and upper-cased for you.
kindNoone_time (the default), renewal or refund. A refund is stored as a negative amount whichever sign you send.
visitor_idNoFrom statsy.visitorId(). The most reliable way to get the sale credited.
emailNoMatched against identities you linked with statsy.identify(), and against known customers.
customer_idNoYour id for the customer. Groups payments into one customer record and a lifetime value.
subscription_idNoTies renewals together and lets a later cancellation mark the customer churned.
occurred_atNoISO timestamp or epoch. Defaults to now. Use the real time, or attribution looks at the wrong touches.
providerNoA label for where it came from. Defaults to api.
rawNoAny JSON object you want kept alongside the payment.

Idempotency

A payment is unique per site and external_id. Send the same one twice and the second call changes nothing and reports duplicate: true; the first insert answers 201, a repeat answers 200. That makes the endpoint safe to retry from a job queue, and safe to replay over a backfill.

The response carries the externalId, whether it was a duplicate, the visitor Statsy matched it to (or null), and the USD amount it was converted to.

Reading payments back

GET /api/v1/sites/<site>/payments lists them, newest first, with from, to, limit (up to 200) and a cursor for paging. GET /api/v1/sites/<site>/customers returns customers with their lifetime value, first touch and status.

Tip. One-off payments do not need code at all. The Revenue page has a form: Record payments by hand.

Last updated · Markdown version