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.

JSON
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. details is label/value rows; download is files with optional expiry; status is a state line; link is a single call to action.
payloadobjectrequired
A title and items. Items carry label, and one of value (with optional copyable) or url (https or mailto, with optional expires_at unix 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 customer and subscription blocks: 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.