Delivery note
Render a delivery note in the background, get a notification when it is done, and bind it together with a PDF your customer supplied. You learn async rendering, webhooks, uploads and merging.
What this teaches
Building it in the editor
- Add the line item table and an acknowledgement signature area, exactly as in the invoice and contract recipes.
- The document itself is done in the editor. What this recipe teaches is what comes next — what you do with the file once it exists.
Building it with the schema and API
- When the document is large or the caller cannot wait, use POST /v1/create-async. It returns a job id immediately and renders from the queue.
- Send a webhook and we POST to it once when the job finishes. https only, and private or internal addresses are rejected before queueing — so you know before it costs you a render.
- The notification carries no download URL. Our URLs are themselves the authorisation, so putting one in a notification hands the document to whoever sees it. Fetch the URL with your own key via GET /v1/jobs/{id}.
- A notification is not evidence — it is unsigned and anyone can post the same shape to your endpoint. Treat it as "go and check", and let GET /v1/jobs/{id} decide what is true.
- Upload the customer PDF with POST /v1/files — the body is the file itself with content-type application/pdf. Up to 10MB and 100 pages, deleted after 24 hours. The page limit is separate on purpose: a 3,000-page PDF can be under 600KB, so size alone would let through a file that will not open on the phone of whoever receives it.
- Before joining anything, call GET /v1/files/{id} to see what it is: page count, per-page size and rotation, and whether it is encrypted. We cannot open an encrypted PDF.
- POST /v1/pdf/merge joins files in the order you list them, up to 20 at a time.
After it is made
Upload the customer PDF
POST /v1/files
(the body is the PDF itself · content-type: application/pdf)See what it is first
GET /v1/files/file_01J…
Join into one file
POST /v1/pdf/merge
{
"files": [
"file_01J…",
"file_01K…"
]
}The finished document
The JSON below is exactly the document those two routes produce. Our tests validate it and check that it renders without warnings, so you can paste it and use it as is.
{
"v": 1,
"unit": "px",
"page": {
"size": "a4",
"orientation": "portrait",
"margin": {
"t": 48,
"r": 48,
"b": 56,
"l": 48
},
"header": "{{ data.company }}",
"footer": "{{ data.no }} · [[page]] / [[pages]]"
},
"fonts": [],
"styles": [
{
"id": "s_h1",
"fontFamily": "Liberation Sans",
"fontSize": 22,
"fontWeight": 700,
"italic": false,
"color": "#111111",
"bg": "transparent",
"align": "left",
"valign": "top",
"lineHeight": 1.5,
"border": "none",
"radius": 0,
"opacity": 1
},
{
"id": "s_l",
"fontFamily": "Liberation Sans",
"fontSize": 12,
"fontWeight": 400,
"italic": false,
"color": "#333333",
"bg": "transparent",
"align": "left",
"valign": "top",
"lineHeight": 1.5,
"border": "none",
"radius": 0,
"opacity": 1
},
{
"id": "s_tbl",
"fontFamily": "Liberation Sans",
"fontSize": 11,
"fontWeight": 400,
"italic": false,
"color": "#111111",
"bg": "transparent",
"align": "left",
"valign": "middle",
"lineHeight": 1.5,
"border": "none",
"radius": 0,
"opacity": 1
}
],
"nodes": [
{
"id": "n_title",
"parent": "",
"order": 0,
"type": "label",
"x": 0,
"y": 0,
"w": 400,
"h": 28,
"show": "",
"styleId": "s_h1",
"props": {
"text": "Delivery note",
"src": "",
"fit": "",
"thickness": 0,
"stroke": "",
"fill": "",
"repeat": "",
"as": "",
"break": "",
"keep": "",
"padding": 0,
"padY": 0,
"padX": 0,
"align": "",
"valign": "",
"wrap": "",
"bind": "",
"headerHeight": 0,
"rowHeight": 0,
"max": 0,
"columns": [],
"rows": 0,
"cells": [],
"chartKind": "",
"codeKind": "",
"ecc": "",
"fieldKind": "",
"fieldName": "",
"options": [],
"required": false,
"readonly": false,
"html": ""
},
"name": "Title"
},
{
"id": "n_meta",
"parent": "",
"order": 1,
"type": "label",
"x": 0,
"y": 34,
"w": 697,
"h": 18,
"show": "",
"styleId": "s_l",
"props": {
"text": "{{ data.customer }} · {{ data.no }}",
"src": "",
"fit": "",
"thickness": 0,
"stroke": "",
"fill": "",
"repeat": "",
"as": "",
"break": "",
"keep": "",
"padding": 0,
"padY": 0,
"padX": 0,
"align": "",
"valign": "",
"wrap": "",
"bind": "",
"headerHeight": 0,
"rowHeight": 0,
"max": 0,
"columns": [],
"rows": 0,
"cells": [],
"chartKind": "",
"codeKind": "",
"ecc": "",
"fieldKind": "",
"fieldName": "",
"options": [],
"required": false,
"readonly": false,
"html": ""
},
"name": "Customer"
},
{
"id": "n_items",
"parent": "",
"order": 2,
"type": "table",
"x": 0,
"y": 76,
"w": 697,
"h": 180,
"show": "",
"styleId": "s_tbl",
"props": {
"text": "",
"src": "",
"fit": "",
"thickness": 1,
"stroke": "#c9ced6",
"fill": "#f3f5f7",
"repeat": "",
"as": "",
"break": "",
"keep": "",
"padding": 0,
"padY": 5,
"padX": 8,
"align": "",
"valign": "middle",
"wrap": "",
"bind": "data.items",
"headerHeight": 26,
"rowHeight": 22,
"max": 100,
"columns": [
{
"header": "Item",
"cell": "{{ item.name }}",
"width": 397,
"align": "left"
},
{
"header": "Qty",
"cell": "{{ item.qty }}",
"width": 120,
"align": "right"
},
{
"header": "Amount",
"cell": "{{ format(item.amount, \"currency\", \"USD\", \"en-US\") }}",
"width": 180,
"align": "right"
}
],
"rows": 0,
"cells": [],
"chartKind": "",
"codeKind": "",
"ecc": "",
"fieldKind": "",
"fieldName": "",
"options": [],
"required": false,
"readonly": false,
"html": ""
},
"name": "Line items"
},
{
"id": "n_rule",
"parent": "",
"order": 3,
"type": "line",
"x": 397,
"y": 300,
"w": 300,
"h": 2,
"show": "",
"styleId": "s_l",
"props": {
"text": "",
"src": "",
"fit": "",
"thickness": 1,
"stroke": "#111111",
"fill": "",
"repeat": "",
"as": "",
"break": "",
"keep": "",
"padding": 0,
"padY": 0,
"padX": 0,
"align": "",
"valign": "",
"wrap": "",
"bind": "",
"headerHeight": 0,
"rowHeight": 0,
"max": 0,
"columns": [],
"rows": 0,
"cells": [],
"chartKind": "",
"codeKind": "",
"ecc": "",
"fieldKind": "",
"fieldName": "",
"options": [],
"required": false,
"readonly": false,
"html": ""
},
"name": "Signature rule"
},
{
"id": "n_ack",
"parent": "",
"order": 4,
"type": "label",
"x": 397,
"y": 306,
"w": 300,
"h": 18,
"show": "",
"styleId": "s_l",
"props": {
"text": "Received by (signature)",
"src": "",
"fit": "",
"thickness": 0,
"stroke": "",
"fill": "",
"repeat": "",
"as": "",
"break": "",
"keep": "",
"padding": 0,
"padY": 0,
"padX": 0,
"align": "",
"valign": "",
"wrap": "",
"bind": "",
"headerHeight": 0,
"rowHeight": 0,
"max": 0,
"columns": [],
"rows": 0,
"cells": [],
"chartKind": "",
"codeKind": "",
"ecc": "",
"fieldKind": "",
"fieldName": "",
"options": [],
"required": false,
"readonly": false,
"html": ""
},
"name": "Acknowledgement"
}
]
}The request body. Put the document above into template and the data below into data.
{
"template": "(the document above)",
"data": {
"company": "BrewMyPDF",
"customer": "Hanbit Corp.",
"no": "DN-2026-0311",
"items": [
{
"name": "Mug",
"qty": 12,
"amount": 180
},
{
"name": "Sticker set",
"qty": 40,
"amount": 120
}
]
},
"webhook": "https://example.com/hooks/BrewMyPDF"
}