splitfriday

API documentation

Your website keeps selling - Splitfriday runs the money. Create an order with the split, redirect the buyer to the returned checkout URL, and listen to webhooks for the rest. All amounts are integer cents. All requests need your API key in the x-api-key header (create one in the dashboard).

Not a developer? This page is the technical reference for your engineer. The human version - setup, QR payments, inviting the people you pay - lives in the setup guides.

Create an order

One buyer payment, split between recipients. parties[].vendorId is the recipient id from the dashboard; shares and line items must add up to the same total. Returns the Stripe Checkout URL to redirect the buyer to. orderRef is your own id - reusing one is rejected, which makes retries safe.

POST https://splitfriday.com/api/orders
x-api-key: sfk_...
Content-Type: application/json

{
  "orderRef": "booking-1042",
  "parties": [
    { "role": "stylist", "vendorId": "cm...", "shareCents": 6000, "commissionableCents": 6000 },
    { "role": "salon",   "vendorId": "cm...", "shareCents": 4000, "commissionableCents": 4000 }
  ],
  "serviceStartAt": "2026-08-01T10:00:00Z",
  "serviceEndAt":   "2026-08-01T11:00:00Z",
  "lineItems": [{ "name": "Haircut + color", "amountCents": 10000, "quantity": 1 }],
  "customerEmail": "buyer@example.com",
  "successUrl": "https://your-site.com/thanks",
  "cancelUrl":  "https://your-site.com/booking/1042"
}

201 -> { "orderId": "cm...", "checkoutUrl": "https://checkout.stripe.com/..." }

The order becomes paid when the buyer completes checkout (you get a webhook). Payouts release automatically after the service ends plus your complaint window (set in Settings, overridable per order with complaintWindowHours, 0-720); your commission is simply never transferred.

Read an order

GET https://splitfriday.com/api/orders/booking-1042
x-api-key: sfk_...

200 -> { "orderRef": "booking-1042", "status": "PAID", "totalCents": 10000,
         "parties": [...], "ledger": [...] }

Statuses: REQUIRES_PAYMENT, PAID, RELEASED, FROZEN, REFUNDED, CANCELED. The ledger lists every money movement with its Stripe reference.

Refunds and cancellation

Buyer cancellation applies your cancellation tiers automatically (refund fraction by hours before service start, frozen onto the order at creation):

POST /api/orders/booking-1042/cancel
-> { "canceled": true, "refundedCents": 5000, "refundFraction": 0.5 }

Direct refunds - partial (amountCents), full (empty body), one recipient's share in full (role and/or vendorId, must match exactly one party - for provider cancellations, never costs the buyer the rest), or a post-release refund via transfer reversals (released: true):

POST /api/orders/booking-1042/refund
Idempotency-Key: refund-booking-1042-attempt-1
{ "amountCents": 3000, "note": "late start" }
-> { "refundedCents": 3000 }

Send an Idempotency-Key header on refunds (any unique string, up to 200 characters). If your call times out, retry with the same key: you get the original response back instead of a second refund. Keys live for 24 hours.

Freeze an order during a complaint (blocks release until resolved):

POST /api/orders/booking-1042/freeze
{ "action": "freeze", "reason": "buyer complaint" }   // or "unfreeze"

Every refund shrinks each recipient's pending share and your commission proportionally, reconciling to the cent.

Webhooks to your site

Configure an endpoint URL in Settings and we POST these events: order.paid, order.released, order.refunded, order.canceled, order.disputed (plus test.ping from the dashboard). Failed deliveries retry with backoff for ~2 days.order.refunded carries external: true when the refund or chargeback happened directly in Stripe rather than through Splitfriday.

POST https://your-site.com/webhooks/splitfriday
splitfriday-signature: t=1760000000,v1=hex(hmac_sha256(secret, t + "." + body))

{ "id": "cm...", "type": "order.paid", "createdAt": "2026-08-01T10:03:22.000Z",
  "data": { "orderRef": "booking-1042", "totalCents": 10000, "currency": "eur" } }

Verify the signature (Node example):

const crypto = require("crypto");
function verify(body, header, secret) {
  const { t, v1 } = Object.fromEntries(header.split(",").map(p => p.split("=")));
  if (Math.abs(Date.now() / 1000 - Number(t)) > 300) return false;
  const expected = crypto.createHmac("sha256", secret)
    .update(t + "." + body).digest("hex");
  return crypto.timingSafeEqual(Buffer.from(expected), Buffer.from(v1));
}

Respond with any 2xx quickly; do your processing async. Deliveries can arrive more than once - use the event id to deduplicate.

The money model

Splitfriday never holds funds. Charges land in YOUR Stripe account (you are the merchant of record); recipient payouts are Stripe transfers created at release time, referencing the original charge. Recipients onboard through Stripe-hosted forms - you never handle their bank details. Monthly commission invoices and annual earner statements generate automatically in the dashboard.

Security and your Stripe key

Your Stripe key is stored AES-256-GCM encrypted, with the encryption key held separately from the database. It is write-only: never displayed, never logged, never returned by any API. You keep the kill switch - rolling the key in your Stripe dashboard instantly cuts our access.

Recommended: connect a restricted key, not your full secret key. In Stripe: Developers > API keys > Create restricted key, granting only:

Checkout Sessions ... Write     Balance ............ Read
PaymentIntents ..... Read      Webhook Endpoints .. Write
Charges ............ Write     Account information  Read
Connect: Accounts, Account Links, Transfers ... Write

A restricted key cannot touch your payout bank account, account settings, or anything beyond running your splits. If a permission is missing, the connect screen shows Stripe's exact error naming it. Stripe OAuth ("Connect with Stripe", no keys at all) is on our roadmap.