curl -X POST https://api.liddie.io/widget/v1/checkout \
-H 'Content-Type: application/json' \
-H 'Origin: https://example.com' \
-d '{
"publishableKey": "pk_live_...",
"widgetId": "<widgetId>",
"amount": "25.00"
}'
// Runs on the embedding page — the browser sends the Origin header itself.
// No credential header: publishableKey + widgetId + allowed origin authorize the call.
const res = await fetch('https://api.liddie.io/widget/v1/checkout', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
publishableKey: 'pk_live_...',
widgetId: widgetId,
amount: '25.00',
}),
})
const { data } = await res.json()
// invoiceUrl is a PATH (`/i/<slug>`); the hosted checkout lives on the
// dashboard host, so prepend it — assigning the bare path would navigate
// to your own site instead.
window.location.href = 'https://dash.liddie.io' + data.invoiceUrl
{ "ok": true, "data": { "slug": "exampleslug2", "invoiceUrl": "/i/exampleslug2", "paymentId": "6a0000000000000000000002" } }
Widgets
Widget checkout (public)
Start a crypto checkout from an embedded Liddie widget using your publishable key — no credential header, authorized by key, widget id and page origin.
POST
/
widget
/
v1
/
checkout
curl -X POST https://api.liddie.io/widget/v1/checkout \
-H 'Content-Type: application/json' \
-H 'Origin: https://example.com' \
-d '{
"publishableKey": "pk_live_...",
"widgetId": "<widgetId>",
"amount": "25.00"
}'
// Runs on the embedding page — the browser sends the Origin header itself.
// No credential header: publishableKey + widgetId + allowed origin authorize the call.
const res = await fetch('https://api.liddie.io/widget/v1/checkout', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
publishableKey: 'pk_live_...',
widgetId: widgetId,
amount: '25.00',
}),
})
const { data } = await res.json()
// invoiceUrl is a PATH (`/i/<slug>`); the hosted checkout lives on the
// dashboard host, so prepend it — assigning the bare path would navigate
// to your own site instead.
window.location.href = 'https://dash.liddie.io' + data.invoiceUrl
{ "ok": true, "data": { "slug": "exampleslug2", "invoiceUrl": "/i/exampleslug2", "paymentId": "6a0000000000000000000002" } }
Starts a checkout from an embedded widget. You get back an
invoiceUrl — send the visitor there and the hosted checkout takes over. The created payment appears in your normal payment list and webhook stream like any other.
How authorization works
There is no credential header. Three things together authorize the request:- The
publishableKey(pk_live_*, safe to expose — see the Authentication guide for fetching and rotating it; rotating invalidates the old one immediately, so update your embeds). - The
widgetId. - The page’s
Originheader matching the widget’sallowedOrigins.
What a publishable key can NEVER do: create widget payments — that’s all. It cannot read payments, balances, or anything else; it grants no access to any other endpoint. Leaking it costs you nothing beyond possible junk invoices (bounded by the origin allowlist + rate limit); rotate it if spammed.
Parameters
string
Sent by the browser for the embedding page. Its hostname must match the widget’s
allowedOrigins — host-equal or a subdomain at any depth (listing example.com also allows shop.example.com) — unless the widget’s allowlist is empty (empty = any origin).string
required
Your publishable key (
pk_live_*).string
required
The widget to check out through.
Optional fields
Optional fields
string | number
Only for
variable widgets: the amount the visitor typed.object
Donor details, per the widget’s donor-field configuration (
donorFields / requiredDonorFields). Recognized keys: name, email, phone, address, comment. Any other key is silently ignored (stripped), not rejected — a misspelled key such as emial produces no error and its value is dropped, so the checkout succeeds with that field missing.string
Optional double-submit protection (8–64 chars of
[A-Za-z0-9_-]). A retry with the same token and an identical payload dedupes to the same invoice instead of minting a second one; a differing payload under the same token deliberately mints a distinct invoice. A malformed token is silently ignored (the request still succeeds, but with no dedupe) — server-side integrators that skip this field get no dedupe at all, so a network retry mints two invoices for one purchase.string | number | boolean | object | array
Arbitrary metadata attached to the created payment. Any JSON value is accepted; it’s stored as informational hints and never overrides system fields.
curl -X POST https://api.liddie.io/widget/v1/checkout \
-H 'Content-Type: application/json' \
-H 'Origin: https://example.com' \
-d '{
"publishableKey": "pk_live_...",
"widgetId": "<widgetId>",
"amount": "25.00"
}'
// Runs on the embedding page — the browser sends the Origin header itself.
// No credential header: publishableKey + widgetId + allowed origin authorize the call.
const res = await fetch('https://api.liddie.io/widget/v1/checkout', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
publishableKey: 'pk_live_...',
widgetId: widgetId,
amount: '25.00',
}),
})
const { data } = await res.json()
// invoiceUrl is a PATH (`/i/<slug>`); the hosted checkout lives on the
// dashboard host, so prepend it — assigning the bare path would navigate
// to your own site instead.
window.location.href = 'https://dash.liddie.io' + data.invoiceUrl
{ "ok": true, "data": { "slug": "exampleslug2", "invoiceUrl": "/i/exampleslug2", "paymentId": "6a0000000000000000000002" } }
Send the visitor to
https://dash.liddie.io + invoiceUrl as soon as the 201 arrives — it’s the hosted checkout for the payment that was just created, and the payment already shows in your payment list and webhook stream.Response
string
The invoice slug.
string
Hosted checkout path, not a full URL — send the visitor to
https://dash.liddie.io + this value.string
The created payment’s id, visible in your normal payment list.
Errors
| Status | Body | Why |
|---|---|---|
| 400 | {"ok":false,"error":{"code":"AMOUNT_BELOW_MINIMUM","message":"...","minimumAmount":"...","minimumCurrency":"..."}} | The typed amount is below the smallest order any currency can serve — show the payer the returned floor. The only failure that names its cause (it describes the visitor’s own input, nothing about the widget). |
| 401 | {"ok":false,"error":{"code":"WIDGET_UNAUTHORIZED","message":"Invalid widget credentials"}} | Shape gate only, before any database lookup: the publishableKey fails the pk_live_ format check, or widgetId is not a valid ObjectId. |
| 401 | {"ok":false,"error":{"code":"WIDGET_CHECKOUT_FAILED","message":"Invalid widget credentials"}} | Well-formed but unknown or rotated key, or a suspended merchant. Note the different code from the row above — a rotated key lands here, not on WIDGET_UNAUTHORIZED. |
| 404 | {"ok":false,"error":{"code":"WIDGET_CHECKOUT_FAILED","message":"Widget not found"}} | The widget is inactive or deleted (under a valid key). This is a 404, not the 401 above — do not key retry/alerting logic on a 401 for a merchant toggling the widget off. |
| 400 | {"ok":false,"error":{"code":"WIDGET_CHECKOUT_FAILED","message":"..."}} | Any other service-side rejection — amount out of the widget’s bounds, unsupported currency, or a field the widget does not allow. The service’s own message is passed through. |
| 403 | {"ok":false,"error":{"code":"WIDGET_CHECKOUT_FAILED","message":"This widget is not allowed on this site"}} | Page origin not in allowedOrigins. |
| 429 | {"ok":false,"error":{"code":"WIDGET_CHECKOUT_FAILED","message":"Too many pending checkouts. Please try again in a few minutes."}} | Per-merchant pending sub-quota (150 pending widget-created payments), separate from the 30/min per-IP rate limit. Under a junk-checkout flood (the pk is public) legitimate visitors can hit this even while each is far under 30/min; pending widget rows auto-cancel after ~1 h. |
See also
- Create a widget — configure
allowedOrigins, amounts and donor fields for this endpoint. - Authentication — where the publishable key comes from and how to rotate it.
- Webhooks & IPN — get notified when a widget-created payment confirms.