Payments API
Sessiq takes online payments through two providers:
- Stripe for cards and wallets. The customer pays in your page with Stripe Elements.
- Paytrail for Finnish online banking and other local methods. The customer is redirected to Paytrail's hosted page.
Each venue uses its own Stripe and Paytrail accounts. Staff connect them in the admin. Bookings can also be paid on arrival, with a deposit, or at the counter.
This page covers taking payment for a booking hold, refunds, and no-show fees. Provider callbacks (Stripe webhooks, Paytrail notifications) are handled by Sessiq; see Webhooks. For gift card payments, see Gift Cards API.
Public endpoints
All of these need the x-tenant-id header and no login. They work on a booking that is hold (or confirmed with an unpaid balance).
| Method | Path | Purpose |
|---|---|---|
| GET | /api/v1/payments/config | Which providers are on, and the Stripe publishable key |
| POST | /api/v1/payments/create-intent | Create a Stripe PaymentIntent for a booking |
| POST | /api/v1/payments/{bookingId}/confirm | Tell Sessiq a Stripe payment succeeded; confirms the booking |
| POST | /api/v1/payments/paytrail/create | Create a Paytrail payment and get the redirect URL |
| GET | /api/v1/payments/paytrail/return | Settle a Paytrail payment when the customer comes back |
| GET | /api/v1/payments/{bookingId}/status | Current payment status |
Payment configuration
GET /api/v1/payments/config?bookingId={bookingId}
{
"publishableKey": "pk_live_xxx",
"providers": {
"stripe": { "enabled": true, "publishableKey": "pk_live_xxx" },
"paytrail": { "enabled": true, "merchantId": "123456" }
}
}
Pass bookingId so the answer reflects the booking's site, which can have its own payment accounts. Initialise Stripe.js with this key, not one of your own.
Pay with Stripe
-
Create a PaymentIntent:
curl -X POST https://api.sessiq.com/api/v1/payments/create-intent \
-H "Content-Type: application/json" \
-H "x-tenant-id: {tenantId}" \
-d '{
"bookingId": "bk_abc123",
"customerEmail": "customer@example.com",
"customerName": "Maija Meikäläinen",
"customerPhone": "+358401234567"
}'{
"success": true,
"clientSecret": "pi_xxx_secret_yyy",
"paymentIntentId": "pi_xxx",
"amount": 8000,
"currency": "eur"
}Field Required Description bookingIdYes The hold to pay for paymentAmountNo Amount in cents (a positive integer), to pay a deposit only. Defaults to the full total customerEmail,customerName,customerPhoneNo Saved on the booking, so it has contact details even if the customer never returns to your page savedPaymentMethodId,stripeCustomerIdNo Pay with a card the logged-in customer saved earlier amountin the response is in cents. Calling this again for the same booking reuses the earlier intent only if it is for the same amount; otherwise the old intent is cancelled and a new one created.The server decides how much may be charged, from the product's current deposit settings:
Booking Allowed paymentAmountNo amount charges Deposit product, nothing paid yet the deposit (1 cent rounding tolerance) up to the full total the full total No deposit, nothing paid yet exactly the full total the full total part_paid(deposit already taken)1 cent up to the outstanding balance the outstanding balance Anything else gets 400 before Stripe is called. Out-of-range amounts return code
PAYMENT_AMOUNT_OUT_OF_RANGEwith the allowedminandmax. Show the deposit frombooking.depositInfo.depositAmount(cents) onGET /api/v1/bookings/{bookingId}: it includes add-ons, while the slot-level deposit in availability responses is only a preview. -
Mount Stripe Elements with
clientSecretand callstripe.confirmPayment()in the browser. -
When Stripe reports
succeeded, call:curl -X POST https://api.sessiq.com/api/v1/payments/{bookingId}/confirm \
-H "Content-Type: application/json" \
-H "x-tenant-id: {tenantId}" \
-d '{ "paymentIntentId": "pi_xxx" }'{
"success": true,
"bookingStatus": "confirmed",
"guestAccessToken": "eyJhbGciOi..."
}This records the payment and confirms the booking. Do not also call
POST /api/v1/bookings/{id}/confirmafterwards: the booking is no longer a hold and that call fails.
If the browser closes before step 3, Stripe's webhook still confirms the booking. Your page can check GET /api/v1/payments/{bookingId}/status when the customer comes back.
Pay with Paytrail
-
Create the payment:
POST /api/v1/payments/paytrail/create
{
"bookingId": "bk_abc123",
"successUrl": "https://example.com/booking/done",
"cancelUrl": "https://example.com/booking/cancelled",
"customerEmail": "customer@example.com",
"customerName": "Maija Meikäläinen",
"customerPhone": "+358401234567"
}The response has
redirectUrl,paymentId,amountandcurrency. -
Send the browser to
redirectUrl. -
Paytrail sends the customer back to
successUrlorcancelUrlwithcheckout-*query parameters. Pass them on to settle the payment straight away:GET /api/v1/payments/paytrail/return?checkout-reference={bookingId}&checkout-status=ok&checkout-transaction-id={transactionId}Forward all the
checkout-*parameters you received, including the signature. The response hassuccess,bookingStatusandmessage.
Paytrail also notifies Sessiq server to server, so the booking is confirmed even if the customer never comes back.
Other ways to pay
Pay on arrival. If the product allows it, confirm the hold with payment_mode: "poa" (see Confirm a booking). The booking is confirmed with payment pending. Staff take the money at check-in (mark-arrived with collectPayment) or record it with mark-paid. Details: Payment on arrival guide.
Deposit. A product can require a deposit: a fixed amount or a percentage, with the balance due some hours before the event or after booking. The deposit amount appears as depositInfo on availability slots and on GET /api/v1/bookings/{bookingId}. To pay only the deposit, send paymentAmount (in cents) to create-intent. The booking becomes confirmed + part_paid, and Sessiq emails the customer a reminder with a payment link shortly before the balance is due. The customer can also pay the rest at the venue. If the product's deposit is set to collect the balance on arrival (balanceDueMode: "on_arrival"), no reminder is sent and the rest is paid at the venue. A deposit product cannot be confirmed without paying at least the deposit online: pay on arrival returns 409 DEPOSIT_REQUIRED.
Zero total. When a discount or voucher covers the whole price, confirm the hold with POST /api/v1/bookings/{id}/confirm. It becomes confirmed and paid with no payment step.
Payment status
GET /api/v1/payments/{bookingId}/status:
{
"success": true,
"status": "succeeded",
"paymentIntentId": "pi_xxx",
"amount": 8000,
"currency": "eur",
"paidAt": "2026-10-15T09:06:12.000Z",
"bookingStatus": "confirmed"
}
status is one of pending, processing, succeeded, failed, canceled or requires_action. The booking's own statuses are described in Bookings API.
Refunds
When a customer or staff member cancels a paid booking, Sessiq applies the cancellation policy and refunds through the original provider. Use GET /api/v1/bookings/{bookingId}/cancel-preview to see the refund before cancelling.
Staff can also refund without cancelling. These endpoints need a staff JWT.
| Method | Path | Permission | Purpose |
|---|---|---|---|
| POST | /api/v1/admin/refunds/process | admin:refunds:process | Refund all or part of a booking |
| POST | /api/v1/admin/bookings/{bookingId}/record-manual-refund | admin:refunds:process | Record cash or external card money handed back |
| GET | /api/v1/admin/bookings/{bookingId}/refunds | admin:refunds:view | A booking's refunds |
| GET | /api/v1/admin/refunds/{refundRequestId} | admin:refunds:view | One refund |
| GET | /api/v1/admin/refunds | admin:refunds:view | Search refunds (status, bookingId, requestedBy, dateFrom, dateTo, minAmount, maxAmount, page) |
Refund a booking
curl -X POST https://api.sessiq.com/api/v1/admin/refunds/process \
-H "Authorization: Bearer {token}" \
-H "x-tenant-id: {tenantId}" \
-H "Content-Type: application/json" \
-d '{
"bookingId": "bk_abc123",
"refundType": "partial",
"amount": 20,
"reason": "Room was too cold",
"adminNotes": "Approved by manager"
}'
| Field | Required | Description |
|---|---|---|
bookingId | Yes | Booking to refund |
refundType | Yes | full, partial or adjustment |
amount | For partial | Amount in euros |
reason | Yes | Shown in the refund record |
adminNotes | No | Internal only |
The response has refundRequestId, status (pending, processing, completed or failed), refundAmount, feeAmount, netRefundAmount and a paymentBreakdown with one line per original payment it refunded.
Manual refunds
For walk-in or phone bookings paid in cash or on an external terminal, the money goes back at the counter. Record it with POST /api/v1/admin/bookings/{bookingId}/record-manual-refund and { "amount": 40, "reason": "..." }. The booking's payment status becomes refunded for a full refund.
No-show fees
A product's (or site's) cancellation policy can set a fee for customers who do not turn up. After staff mark a booking as no-show, they can charge the fee.
| Method | Path | Purpose |
|---|---|---|
| POST | /api/v1/admin/no-show/booking/{bookingId}/calculate-fee | Preview the fee without charging |
| POST | /api/v1/admin/no-show/booking/{bookingId}/process-fee | Charge the fee and notify the customer |
| GET | /api/v1/admin/no-show/booking/{bookingId} | No-show record for a booking |
| GET | /api/v1/admin/no-show/pending | Fees not yet handled |
| PATCH | /api/v1/admin/no-show/{trackingId}/resolve | Close a no-show record by hand |
| GET | /api/v1/admin/no-show/statistics | No-show figures |
These need a staff JWT with admin:write.
process-fee body:
{ "auto_charge": true, "notify_customer": true }
auto_charge and notify_customer default to true. The acting admin is taken from the token; a marked_by_user_id (or, on resolve, resolved_by_user_id) in the body is ignored. resolve takes an optional resolution_notes. The response has success and data with fee_amount (cents), fee_amount_formatted, payment_intent_id, charged_at and notification_sent.