One authenticated POST returns a checkout URL. You redirect the customer to it, they pay on a Stripe Checkout page and return to your store, and a signed webhook confirms the order. Then swap the test token for live.
Your server creates a payment link and gets back a checkout URL; you redirect the customer to it, they pay on a Stripe Checkout page and come back to your store, and a signed webhook tells you the order is paid. The examples below use a placeholder test token; nothing is charged.
1. Create a link
One POST returns a checkoutUrl
2. Customer pays
On a Stripe Checkout page
3. Back to your store
Returned to your returnUrl
4. Order confirmed
Signed webhook completes it
Each site has its own test and live token. Sign in to your BrokkrPay dashboard to copy your real token — the placeholder below shows the format.
The token is a server-side secret — it creates real payment links. Keep it out of browser code and version control.
One authenticated POST with the order total. Send an Idempotency-Key so a retry can never create a second order. The response carries the checkoutUrl you redirect the customer to.
The response carries the URL you redirect the customer to:
| Body field | Requirement | Description |
|---|---|---|
| amount | Required | Order total in whole currency units — not cents. 180 charges $180.00. Decimals are rejected. |
| currency | Required | ISO currency code. Currently USD. |
| customerEmail | Optional | Not needed — the customer enters their email on the payment page, and we record it on the order once they pay. Send it anyway to prefill the payment page. |
| returnUrl | Recommended | Your checkout page. Once the customer finishes on the hosted payment page they are sent here with ?orderId=…&status=success (or status=cancelled), so they land back in your store. Stored exactly as you write it — hash-routed single-page apps need a path-based URL, see step 3. |
| reference | Recommended | Your own order or cart ID, up to 200 characters. Echoed back as reference in every webhook for this order. Not required to be unique — we never deduplicate on it, and an order created without one gets reference: null. |
amount is whole currency units, not cents
Most processors take minor units. We do not. 180 charges $180.00. If you send 18000 expecting $180.00 you will charge $18,000 — that is a valid amount and we will accept it.
Which amounts are accepted
Your customer pays on a hosted page whose line items come from our storefront’s product catalog — you never register products with BrokkrPay. So the total has to be reachable as a sum of the prices in that catalog (repeats allowed). It is dense at ordinary retail amounts, so in practice this only bites on unusual totals; when it does, creation fails with a 400 naming the amount, before the customer ever sees a payment page. Round to a nearby amount, or ask us to add the price point.
The create response carries checkoutUrl. Send the browser there; the customer pays on a Stripe Checkout page and is returned to the returnUrl you set, back inside your own store.
Redirect the top-level page
window.location.href = checkoutUrl, or an ordinary link. Not a popup — it gets blocked — and not an iframe, which breaks Apple Pay and Google Pay. The URL is opaque and single-use; treat it as the whole integration, because there is no BrokkrPay script to load.
Show the retail-partner disclosure before the customer commits
You own the page the customer clicks from, so the disclosure about how the purchase appears on their statement has to be rendered next to your pay button. Skipping it is what turns a legitimate charge into a dispute.
The return trip goes through our storefront and out again. Stripe hands the customer to the storefront, which immediately forwards them to your returnUrl — they only pass through it, they never browse it.
Both outcomes come back to the same URL, distinguished by the status param:
| Paid | …/checkout/complete?orderId=<uuid>&status=success |
| Cancelled | …/checkout/complete?orderId=<uuid>&status=cancelled |
Treat status as a display hint only — the webhook in the next step is what tells you an order is paid. A cancelled payment leaves the order PENDING and the link still valid for 24 hours, so the customer can be sent to it again and finish. Links expire after 24 hours, at which point the order becomes CANCELLED.
Single-page apps: use a path, not a hash
We append ?orderId=…&status=… to your returnUrl exactly as you wrote it. If your URL contains a #, the query string ends up inside the fragment where location.search can’t read it. Put your own reference in the path and redirect server-side:
A real endpoint, not just a fallback: your status page needs it whenever a webhook is delayed, and support needs it to answer 'did this actually go through?'.
GET https://api.brokkrpay.com/api/orders/<orderId> — authenticated with the same site token as everything else. The token scopes the lookup, so it only ever returns your own orders.
Responds with:
Keep the orderId ↔ reference mapping
This endpoint is keyed by our orderId, while your own records are keyed by your reference. Store the two against each other when the create call returns — without it you can receive a webhook you cannot look up, or hold a reference you cannot query.
Never call this from the browser — the token is a server-side secret. Proxy it from your own server, and poll no more than once every few seconds per order. It is a supplement to the webhook, not a replacement: the webhook is still what tells you to fulfill.
BrokkrPay POSTs to your server on every order state change — this is how you reliably fulfill orders. Save your endpoint URLs per site in your BrokkrPay dashboard.
Saved per site, and per environment: you configure a separate test and live endpoint. Test orders only ever reach the test endpoint and live orders only ever reach the live one, so a test payment can never trigger a real fulfillment. Point both at the same URL if you would rather branch on the mode field yourself. Your dashboard has a Send test event button and a delivery log showing the status each attempt returned.
Every state change arrives as a JSON POST:
Handle it and acknowledge quickly (within 5 seconds):
Order states
Ordering: a webhook can arrive before the create call returns
The first PENDING event is emitted as soon as the order exists — which is before POST /api/payment-links has finished responding to you. If your handler persists the order only after that response, there is a window in which an arriving event refers to an order you have never heard of. Usually that is the harmless PENDING, but a fast payer or a slow response can make it SUCCESS. Persist your order before calling us, and buffer events for unknown orderIds for a few seconds rather than dropping them.
Retries and acknowledgement
Reply within 5 seconds. We treat any 2xx as delivered and do not currently retry — a non-2xx, a timeout or a connection failure is recorded in your dashboard’s delivery log and not sent again. So acknowledge first and do your fulfillment work after, and use the delivery log (plus GET /api/orders/:orderId) to catch anything your endpoint missed while it was down.
Verify the signature on every delivery
Every webhook carries a Brokkr-Signature header: an HMAC over the raw request body, keyed by the whsec_… secret shown next to your endpoint in the dashboard. Verify it before parsing the JSON — most frameworks consume the body during parsing and leave you nothing to verify against. Secrets are per environment, so a test delivery can never validate against your live secret. Also check the mode field (test or live) as a second line of defence, and treat the webhook — never the customer returning to your site — as the signal to fulfill.
Run an end-to-end payment in test mode. Going live needs BrokkrPay approval.
Test card
4242 4242 4242 4242
Any future expiry, any CVC. Test orders never charge a real card.
Going live
Once BrokkrPay approves your site for live payments, switch to Live and swap your bpk_test_… token for the bpk_live_… token on the same site. Everything else stays identical.
Prefer to explore first? Your BrokkrPay dashboard has a demo playground that creates a real payment link with live webhook delivery, no code required.
Tell us your vertical and your monthly volume. Approved operators are live on their own Stripe account within a day.