Turn every Tally form response into a PDF
Tally form to PDF, automatically. Turn each submission into a branded PDF quote, receipt, certificate or summary with a webhook, n8n, Make or Zapier.
Quick answer. In Tally, open Integrations → Webhooks and point it at n8n, Make or your own endpoint. Build HTML from data.fields, send it to POST https://html-pdf.com/v1/pdf, and email or store the PDF.
What you'll build
Tally is free for unlimited forms, which makes it a favorite for client intake, quote requests, event sign-ups and course feedback. What it doesn't do is hand anyone a designed PDF afterwards.
This setup adds that step. Each submission becomes a document with your name and layout, sent to the respondent, your team or a Drive folder.
What you need
- A Tally form. Webhooks work on the free plan.
- n8n, Make or Zapier, or a small server endpoint.
- A design. Start from the quote template, receipt template or certificate template.
- An API key from html-pdf.com. You get 100 free pages once, without a card.
What Tally sends
This is the shape of the webhook body, trimmed. Every answer is in data.fields, with the question as label.
{
"eventType": "FORM_RESPONSE",
"data": {
"formName": "Quote request",
"fields": [
{ "key": "question_1", "label": "Company", "type": "INPUT_TEXT", "value": "Acme Ltd" },
{ "key": "question_2", "label": "Email", "type": "INPUT_EMAIL", "value": "jo@acme.example" },
{ "key": "question_3", "label": "Budget", "type": "MULTIPLE_CHOICE", "value": ["opt_2"],
"options": [{ "id": "opt_1", "text": "Under 5k" }, { "id": "opt_2", "text": "5k to 10k" }] }
]
}
}
Choice questions send option ids, so look the text up in options.
Step by step with n8n
Want it ready-made? Download tally-form-to-pdf.json and import it into n8n. The steps below explain what it does.
- Add a Webhook node, method
POST. Copy the production URL into Tally under Integrations → Webhooks. - Add a Code node that turns the fields into HTML.
const { formName, fields } = $json.body.data;
const esc = s => String(s ?? "").replace(/[&<>"]/g, c => ({ "&": "&", "<": "<", ">": ">", '"': """ })[c]);
const text = f => Array.isArray(f.value)
? f.value.map(id => (f.options || []).find(o => o.id === id)?.text ?? id).join(", ")
: f.value;
const rows = fields.map(f => `<tr><th>${esc(f.label)}</th><td>${esc(text(f))}</td></tr>`).join("");
const html = `<!doctype html><html><head><meta charset="utf-8"><style>
body { font-family: system-ui, sans-serif; color: #111; }
th { text-align: left; color: #666; width: 35%; padding: 8px 0; vertical-align: top; }
td { padding: 8px 0; } tr { border-bottom: 1px solid #eee; break-inside: avoid; }
table { width: 100%; border-collapse: collapse; }
</style></head><body><h1>${esc(formName)}</h1><table>${rows}</table></body></html>`;
return [{ json: { html, email: fields.find(f => f.type === "INPUT_EMAIL")?.value } }];
- Add an HTTP Request node:
POST https://html-pdf.com/v1/pdf, headerAuthorization: Bearer hp_live_..., JSON body{"html": "{{ $json.html }}", "format": "A4", "margin": "18mm"}, and Response Format File. - Add Gmail or Send Email, attach the
databinary, and send it to{{ $('Code').item.json.email }}.
Full node settings are in the n8n guide.
With Make or Zapier
Both have a Tally app with a new response trigger, so you don't need the webhook setup.
- Trigger. Tally, new response, pick your form.
- Make the PDF. Make's HTTP → Make a request or Webhooks by Zapier → Custom Request:
POST https://html-pdf.com/v1/pdfwith the JSON below, mapping each answer into the HTML. - Deliver. Map the returned
urlinto a Gmail attachment or a Google Drive upload.
{
"html": "<h1>Quote request</h1><p><b>Company:</b> Acme Ltd</p><p><b>Budget:</b> 5k to 10k</p>",
"format": "A4",
"filename": "quote-request.pdf",
"response": "url"
}
The link in url works for 24 hours. Step-by-step: Make guide · Zapier guide.
Code path for developers
This Express endpoint checks the signature, renders the PDF, then replies. In production, reply to Tally right away and render in a background job.
import crypto from "node:crypto";
import express from "express";
const app = express();
app.post("/tally", express.raw({ type: "application/json" }), async (req, res) => {
const sig = crypto.createHmac("sha256", process.env.TALLY_SECRET).update(req.body).digest("base64");
if (sig !== req.get("tally-signature")) return res.sendStatus(401);
const { data } = JSON.parse(req.body);
const html = renderQuote(data.fields); // your template, with escaped values
const r = await fetch("https://html-pdf.com/v1/pdf", {
method: "POST",
headers: { Authorization: `Bearer ${process.env.HTML_PDF_KEY}`, "Content-Type": "application/json" },
body: JSON.stringify({ html, response: "url" }),
});
const { url } = await r.json();
// Email the link or download and store the file.
res.sendStatus(200);
});
Set a signing secret in the Tally webhook settings so you can check the Tally-Signature header.
Ask your AI agent
With the html-pdf.com MCP server connected to Claude or ChatGPT, paste one sample submission and ask for a design.
Here is a Tally quote request. Use html-pdf to make a one page A4 quote for "Brightside Studio" with the client's details at the top, three line items based on their budget, and a 30 day validity date.
When you like it, ask for the HTML and drop it into the Code node.
FAQ
Can Tally create a PDF of each response on its own?
Tally shows and exports responses, but it doesn't create a designed PDF per submission. A webhook plus an HTML to PDF API adds that.
Do Tally webhooks work on the free plan?
Yes. Webhooks are available on Tally's free plan, so the whole setup can run on n8n's self-hosted edition and the 100 free html-pdf.com pages.
How do I show the text of a multiple choice answer?
Choice fields send option ids in value. Match each id against the options array of the same field to get the text.
Is the response data stored by html-pdf.com?
No. HTML is not stored, and download links expire after 24 hours.
