Build onSignFlow.
The app runs on its own public REST API, so everything you do in SignFlow you can do from code: build templates, send documents, follow every signer.
curl https://signflow-api.leonai.io/api/v1/submissions \
-H "Authorization: $SIGNFLOW_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"template_id": "tpl_abc123",
"name": "NDA — Acme Corp",
"signers": [
{ "name": "Dana Levi", "email": "dana@acme.example", "role": "signer1" }
],
"fields_data": { "company_name": "Acme Corp" }
}'const res = await fetch("https://signflow-api.leonai.io/api/v1/submissions", {
method: "POST",
headers: {
Authorization: process.env.SIGNFLOW_API_KEY,
"Content-Type": "application/json",
},
body: JSON.stringify({
template_id: "tpl_abc123",
name: "NDA — Acme Corp",
signers: [{ name: "Dana Levi", email: "dana@acme.example", role: "signer1" }],
fields_data: { company_name: "Acme Corp" },
}),
});
const { data } = await res.json();
console.log(data.signing_url);import os, requests
res = requests.post(
"https://signflow-api.leonai.io/api/v1/submissions",
headers={"Authorization": os.environ["SIGNFLOW_API_KEY"]},
json={
"template_id": "tpl_abc123",
"name": "NDA — Acme Corp",
"signers": [{"name": "Dana Levi", "email": "dana@acme.example", "role": "signer1"}],
"fields_data": {"company_name": "Acme Corp"},
},
)
print(res.json()["data"]["signing_url"])One request, one signing link
One header: your API key in Authorization, with no Bearer prefix.
Prefill any field of the template with fields_data.
Set signing_order for signers in turn, or leave out the email and deliver the link yourself.
The answer:
201 Created
{
"data": {
"id": "…",
"status": "pending",
"signing_url": "https://signflow.leonai.io/sign/…",
"expires_at": "…",
"signers": [ … ]
}
}Know the moment it's signed
submission.createdsubmission.sentsubmission.signedsubmission.completedsubmission.declinedsubmission.voidedsubmission.expiredsubmission.payment_completedsubmission.card_savedsubmission.payment_failedsigner.openedsigner.viewedsigner.signedsigner.declinedsigner.reminder_sentdocument.generateddocument.downloaded
Every delivery is signed with your endpoint's secret. Check it before you trust the body.
HTTPS only. Private and loopback addresses are refused.
Add your own headers per endpoint, stored encrypted, for a firewall rule to match.
A failed delivery is retried automatically, and you can resend it from the delivery log or the API.
import crypto from "node:crypto";
// X-LeonAI-Signature: t=<unix seconds>,v1=<base64 HMAC-SHA256>
export function verifyWebhook(rawBody, header, secret) {
const parts = Object.fromEntries(
header.split(",").map((p) => [p.slice(0, p.indexOf("=")), p.slice(p.indexOf("=") + 1)]),
);
const expected = crypto
.createHmac("sha256", secret)
.update(`${parts.t}.${rawBody}`)
.digest("base64");
const fresh = Math.abs(Date.now() / 1000 - Number(parts.t)) < 300;
return (
fresh &&
expected.length === parts.v1?.length &&
crypto.timingSafeEqual(Buffer.from(expected), Buffer.from(parts.v1))
);
}What the app does, the API does
- Templates
- Template Fields
- Submissions
- Documents
- Signer Payments
- Webhooks
- Files
- Organizations
- Members & Invites
- Roles & Permissions
- API Tokens
- Usage & Credits
- Audit Log
- SSO
- Connect
Or connect it without code
Make.com
A native app: send documents and act on every signature from your scenarios.

Priority ERP
SignFlow Connect links your Priority company to your signing flows.
Any system with a URL
Point a webhook at it and receive every event as signed JSON.