Become a merchant
A one-time setup. Do this in the SharePay dashboard (recommended) at
/merchant, or via the API.
SharePay opens Stripe Standard connected accounts with a country of GB,
and splits settle in gbp. The country is fixed rather than derived from
anything you send, so a business that cannot complete UK Stripe onboarding
cannot be onboarded here yet.
Register
POST /api/merchants/register with { name }. It authenticates a signed-in
SharePay session (Authorization: Bearer <supabase token>), not an API key,
because it is the call that issues you one. You may also send webhook_url
here instead of setting it later.
Returns 201 with { id, name, api_key, webhook_url, webhook_secret }. The
API key (sp_sk_...) authenticates your server's hosted-checkout calls. The
webhook secret signs the events we send to you, so you can verify they came
from SharePay. See Webhooks.
One merchant per user. Registering a second returns
409 {"code":"merchant_exists","error":"You already have a merchant account"}.
Both credentials stay retrievable: the Developers page in your dashboard
reveals them, as does GET /api/merchants/credentials for the logged-in owner.
Store them as server-side secrets anyway, keep them out of client code and
version control, and rotate either one if it is ever exposed. Rotating
invalidates the old value immediately.
Rotation is an endpoint as well as a button:
POST /api/merchants/credentials/rotate
{ "target": "api_key" }
target must be api_key or webhook_secret, otherwise
400 target must be 'api_key' or 'webhook_secret'. Returns
{ target, value } carrying the new secret.
Connect Stripe
POST /api/merchants/connect returns { url }, a Stripe hosted onboarding
link for a Standard account. Complete it. When Stripe confirms the account,
your merchant flips to stripe_charges_enabled and you can start taking
splits.
That link is one-shot and expires in about five minutes, so never store it. If
yours goes stale, POST /api/merchants/connect/refresh mints a fresh one for
the same account. Calling it before you have started onboarding returns
400 {"code":"no_stripe_account","error":"Start onboarding via /connect first"}.
Check your status any time with GET /api/merchants/me, which returns id,
name, webhook_url, allowed_domain, stripe_account_id,
stripe_charges_enabled, stripe_details_submitted, stripe_connected_at and
created_at. Poll this rather than waiting to hear from us. When the
account still looks incomplete, /me reconciles straight from Stripe on read,
so it is right even if Stripe's account.updated event lagged or never
arrived.
Stripe Connect can decline for reasons outside your control, and it decides
that inside its own hosted flow. SharePay is not notified when that happens,
so nobody will contact you about it. If you come back from Stripe and
stripe_charges_enabled is still false, contact support.
Our team is notified only when SharePay itself cannot start onboarding, that is
when POST /api/merchants/connect fails with a 502. Either way the merchant
record you registered is kept, and POST /api/merchants/connect is idempotent:
calling it again reuses the same connected account rather than opening a second
one.
Limits
Only the routes in the table below are rate-limited, and only those routes
carry draft-7 RateLimit-* headers. Everything else answers without them, so
read your remaining budget off a limited route rather than off any response.
GET /api/merchants/me, POST /api/merchants/connect,
PATCH /api/merchants, GET /api/merchants/webhooks/deliveries and
POST /api/checkout-splits have no limiter and no headers.
| Endpoint | Limit | Keyed by | Body on 429 |
|---|---|---|---|
POST /api/merchants/checkout/sessions | 120 per minute | Your API key | Too many checkout session requests. Please slow down. |
POST /api/merchants/credentials/rotate, POST /api/merchants/webhooks/test, POST /api/merchants/webhooks/deliveries/:id/resend | 5 per 10 minutes | Your IP, shared with the routes below it | Too many requests. Please try again later. |
GET /api/checkout-splits/pay/:token, GET /api/merchants/checkout/sessions/:token | 60 per minute | Caller IP | Too many requests. Please try again shortly. |
POST /api/checkout-splits has no rate limit.
The 5-per-10-minutes bucket is the one you meet first while wiring up webhooks, because test sends, resends and key rotations all draw on it and it is keyed by IP rather than by account.
It is wider than those three routes, too. The same limiter guards the public
write endpoints POST /api/contactus, POST /api/waitlist and
POST /api/partner-inquiries, and the count is per IP across all six. So five
requests spread over a contact-form submission and a couple of webhook test
sends from the same address will exhaust it just as fast as five rotations.
Errors on the merchant endpoints
These apply across the session-authenticated routes under /api/merchants.
| Status | Body | Cause |
|---|---|---|
401 | Unauthorized | Missing, malformed or expired Authorization: Bearer header |
403 | {"code":"consent_required","error":"Consent required", ...} | The signed-in account has not accepted the current terms or privacy policy. Accept them in the app or on the website, then retry |
404 | No merchant for this user | You have not registered a merchant |
404 | {"code":"no_merchant","error":"Register a merchant first"} | The same, from the Stripe Connect routes |
409 | {"code":"merchant_exists","error":"You already have a merchant account"} | Registering a second merchant |
400 | Merchant name is required | name missing on register |
400 | Invalid webhook_url: <reason> | webhook_url failed the URL checks |
400 | {"code":"no_stripe_account","error":"Start onboarding via /connect first"} | connect/refresh before connect |
502 | Could not start Stripe onboarding or Could not refresh Stripe onboarding link | Stripe rejected the account-link request |
500 | Something went wrong! | Unhandled server error |