Customer blocks
Everything so far lives inside your own frame. A block is how your app puts what it knows about a record onto that record’s page — the buyer’s receipt and, in the dashboard, the merchant’s transaction, customer or subscription page — without drawing anything itself. An iframe cannot go in an email and third-party scripts have no business near a payment page, so a block is structured data, sanitized on the way in and rendered by Paysio with its own components, attributed to your app by name. On the merchant’s pages it sits in the main column as a section of its own, between the page’s panels and its activity, as if it were ours.
POST /v1/blocks
{
"object_type": "transaction",
"object_id": "<transaction id>",
"type": "download",
"position": 0,
"payload": {
"title": "Your licence key",
"items": [
{ "label": "Key", "value": "XXXX-YYYY-ZZZZ", "copyable": true },
{ "label": "Manual.pdf", "url": "https://files.example.com/manual.pdf", "expires_at": 1790000000 }
]
}
}object_type'transaction' | 'customer' | 'subscription'required- What the block hangs on. A transaction block reaches the buyer and the merchant; a customer or subscription block reaches the merchant on that record’s page.
object_idstringrequired- Must belong to the token’s workspace; otherwise
404 resource_missing, never a hint that it exists elsewhere. type'details' | 'download' | 'status' | 'link'required- The rendering.
detailsis label/value rows;downloadis files with optional expiry;statusis a state line;linkis a single call to action. payloadobjectrequired- A
titleanditems. Items carrylabel, and one ofvalue(with optionalcopyable) orurl(https or mailto, with optionalexpires_atunix seconds). positionnumber- 0–999, orders your blocks among each other.
Re-posting the same type for the same object corrects it rather than adding a second panel — the unique key is (install, object, type) — so a webhook handler that retries is safe. GET /v1/blocks?object_type=&object_id= lists your blocks on a target; DELETE /v1/blocks/:id removes one.
Where they appear
- Confirmation page — immediately after payment, while the buyer is still there. If your webhook is still running when they land, the page waits for you for up to 30 seconds rather than showing a receipt with a gap in it.
- Customer portal — so they can come back later and still reach it.
- Receipt email — rendered into both the HTML and plain-text bodies.
- The transaction page — the merchant’s own view of the payment, under your app’s name, with a link back to your app.
- The customer and subscription pages — for
customerandsubscriptionblocks: what your app knows about this person or this plan, where the merchant is already looking at them.
A block whose links have all expired stops rendering on its own. Blocks disappear the moment your app is uninstalled or suspended. There is no scope for this endpoint: the write is bounded by your own install, and the object must be the workspace’s.
What we will not render
Everything is sanitised on write: markup is stripped, control and bidirectional characters are removed, URLs must be https or mailto, and lengths are capped. You are sending data, not HTML — which is exactly why it can be trusted next to a payment.
Timing
The confirmation page renders within a second of payment. To have your block there when the buyer lands, post it from your payment.completed webhook handler; if it arrives a moment later the page picks it up on its next refresh, and the portal and the email always have it.