Skip to content
For the first ten businesses: six months of Stippa free, in exchange for your feedback.Find out more
Stippa
FeaturesPricingDemoHelpContact
Log inStart free

Getting started

  • Introduction
  • Authentication
  • Permissions

Core concepts

  • Errors
  • Pagination
  • Idempotency
  • Rate limits

Resources

  • Appointments
  • Customers
  • Services
  • Staff
  • Availability
  • Products
  • Payments
  • Webhooks

Recipes

  • Recipe: book from your own website
  • Recipe: sync the diary to a spreadsheet
  • Recipe: a Slack message on every new booking

Reference

  • Changelog
  • OpenAPI 3.1 document

Payments

Money taken for appointments, however it was taken, and why nothing here is writable.

Last updated 27 October 2026

A payment is money taken for an appointment. This resource is read-only, and unlike services it is read-only for a reason that is about risk rather than about ownership: no API key can move money, at any plan, with any scope, and no scope will be added that changes it.

MethodEndpointScopePlan
GET/api/v1/payments

List payments

Query: limitcursorappointmentIdstatusfromto

payments:readGroei
GET/api/v1/payments/{id}

Retrieve a payment

payments:readGroei

What you get back

{
  "id": "10b7ee1f-eecd-4527-a105-3d7c33fdbfd7",
  "appointmentId": "8db4ab12-82e8-4205-8aea-cfd81dee55d5",
  "kind": "settlement",
  "amountCents": 1750,
  "currency": "EUR",
  "status": "succeeded",
  "source": "manual",
  "refundedAmountCents": 0,
  "createdAt": "2026-08-11T11:04:00.000Z",
  "updatedAt": "2026-08-11T11:04:00.000Z"
}

There is no customer on a payment, and no card detail of any kind: no last four digits, no brand, no expiry, no cardholder name. Card data never reaches our servers, which is what keeps the business out of PCI scope, so there is nothing to expose here even if it were wise to. Resolve the customer through appointmentId, when there is one.

appointmentId is the online link, and is null for money taken in person

A payment taken online is raised for a booking, so it names one. A payment taken in person belongs to a receipt: the business rings up a sale at the counter, and that sale may or may not have started as an appointment. Those rows answer appointmentId: null.

A counter payment does not report the appointment its receipt was opened for, even when there was one. A receipt can be settled more than once, part cash and the rest on a card, so copying the receipt's link onto each payment would put several payments under one appointment with nothing on this shape to tell them apart. The link lives on the receipt.

Two consequences to design around:

  • ?appointmentId= returns the money taken online for that booking. It is not "everything paid for this appointment".
  • Reconciling in-person turnover is a job for the receipt rather than for this endpoint. Receipts are not on this API yet; until they are, source and the from/to window are what a day's takings are summed with.

source tells you who processed the money

sourceMeans
stripeProcessed by Stripe: online, or on a Stripe card reader at the counter
mollieProcessed by Mollie: online, or on a Mollie card reader at the counter
manualThe business recorded a payment it took some other way

source names the payment provider, not the channel. A card tapped on the business's own reader at the counter is a stripe or mollie row just as an online checkout is. Tell the two apart with appointmentId, which is set for money taken online and null for money taken in person.

manual is the row an integrator most often mishandles, and it is the majority of rows for a lot of salons: cash at the counter, a card machine that is not ours, a bank transfer. It is a real payment that we did not process, so there is no provider reference to reconcile it against and no webhook from a payment provider behind it.

A manual payment with status: "succeeded" is as settled as a Stripe one. If you are reconciling takings, count it. If you are matching against a Stripe report, exclude it, or you will spend an afternoon looking for transactions that were never in Stripe.

Statuses

statusMeans
pendingStarted and not settled: an open online checkout, or a card reader still waiting for the card. It may never settle
succeededSettled. The money is there
failedIt did not go through
refund_pendingA refund has been issued and the provider has not confirmed it
partially_refundedSome of it came back
refundedAll of it came back

Six values, and the list is documented as extensible: a seventh is an additive change and ships without notice, so route anything you do not recognise to a default branch rather than throwing. See the compatibility rules.

A pending row is not a promise. A customer who opened a Mollie checkout and closed the tab leaves one behind forever, so a pending payment older than a day or so is almost certainly abandoned rather than in flight. Nothing sweeps them, because the row is the record that somebody tried.

A refund is a row of its own, and its amount is negative

A refund is not only an edit to the payment it reverses. It is also a row of its own, with kind: "refund" and a negative amountCents.

{
  "id": "3fbb0a41-6c1b-4a2c-9a2d-4b21c5f0e9aa",
  "appointmentId": "8db4ab12-82e8-4205-8aea-cfd81dee55d5",
  "kind": "refund",
  "amountCents": -500,
  "currency": "EUR",
  "status": "succeeded",
  "source": "stripe",
  "refundedAmountCents": 0,
  "createdAt": "2026-08-13T09:20:00.000Z",
  "updatedAt": "2026-08-13T09:20:00.000Z"
}

The consequence to design around: GET /api/v1/payments returns negative rows, so summing the refund rows in with the rest gives you the net rather than the gross. If you want the gross, drop the kind: "refund" rows before summing. There is no kind query parameter, so that is a filter you apply to the page you were given rather than one you ask for.

The original row still records the refund too:

  • refundedAmountCents: 0: nothing was returned
  • less than amountCents, with status partially_refunded: the common case when a business keeps a deposit against a late cancellation
  • equal to amountCents, with status refunded: fully refunded

So there are two ways to compute what a business kept, and you must not combine them. Either sum amountCents across the settlement and refund rows, or sum amountCents minus refundedAmountCents across the settlement rows only. Doing both subtracts each refund twice.

refund_pending is the state worth thinking about once rather than never: the business has issued the refund and the provider has not confirmed. The money is leaving and has not left. No reversing row exists yet at that point, because the reversal is written when the provider confirms. Reconciliation that treats it as settled is early by a day; reconciliation that treats it as succeeded revenue is wrong.

kind says what the money is

kindMeans
settlementMoney taken for an appointment or a receipt
refundA reversal of an earlier payment. amountCents is negative
tipA tip given at the counter on top of a settlement, with that settlement's source. Positive, and not turnover
gift_card_redemptionPart or all of a receipt paid with a gift card. source is manual

All four are in the list today, and the list is extensible: route a value you do not recognise to a default branch rather than throwing.

⚠️

Summing every amountCents in a window does not give you turnover. Tips are in the list, as positive rows, and they are money the staff earned rather than money the business sold. Leave out the tip rows when you reconcile revenue. A gift_card_redemption is a sale paid with a voucher that was itself sold earlier, so count either the voucher's sale or its redemption, not both.

Filtering

from and to filter on creation time, which is when the payment was taken. That is the opposite of the appointments list, where they filter on start time, and the difference is deliberate rather than an inconsistency: an appointment is an event in the future and a payment is an event that already happened. A monthly reconciliation wants payments taken in August, not payments attached to appointments happening in August.

appointmentId and status are exact matches. There is no updatedSince here, so a payment that settles later is found by re-reading a window rather than by an incremental sync. The payment.succeeded webhook is the better answer to that problem: see webhooks.

What is not here

Charging. There is no endpoint that takes money. A booking that needs prepayment returns a checkout URL or a client secret from POST /api/v1/appointments and the customer settles it themselves; see the appointments page.

Refunding. Not on this API, deliberately, and it is on the deny list at permissions. A refund is irreversible and moves real money out of a business's account, and a leaked key that could issue them is a leaked key that can empty a salon's balance. It stays behind a logged-in owner or admin in the dashboard.

Payouts and provider balances. Between the business and its payment provider, and nothing to do with this surface.

A business with no payment provider connected is answered like any other: a business that takes only cash returns its manual rows, and one that has recorded nothing returns an empty list.

On this page

  • What you get back
  • appointmentId is the online link, and is null for money taken in person
  • source tells you who processed the money
  • Statuses
  • A refund is a row of its own, and its amount is negative
  • kind says what the money is
  • Filtering
  • What is not here
Stippa

Appointment scheduling, without the hassle.

Try free for 1 month

Product

  • Features
  • Pricing
  • Demo
  • FAQ
  • Contact
  • System status

Features

  • Booking widget
  • Online payments
  • Reminders
  • No-show prevention
  • Online calendar
  • Your own website
  • Client management

For whom

  • For salons
  • Beauty salons
  • Physiotherapists
  • Coaches
  • Personal trainers
  • Barbershops
  • Nail salons
  • Pedicurists
  • Massage salons

Legal

  • Privacy policy
  • Terms of service
  • Data processing agreement

© 2026 Stippa. All rights reserved.

De Rechter Software · Molenwater 20, 4511 BN Breskens · KvK 98466402· VAT NL005332100B80 · +31 6 15745447

······
Try free for 1 month

1 month free. No card needed.

Cookies

We use cookies to measure how visitors use this website. With your consent we also record your visit, so we can see where something goes wrong.

You can change your choice at any time via 'Cookie preferences' at the bottom of every page.

· Learn more