Webhook payload structure
How webhook payloads work
Section titled “How webhook payloads work”When Tallyfy sends a webhook to your endpoint, it delivers a JSON payload with data about the event. The structure depends on the webhook type - process-level webhooks (fired when a process launches or completes) versus task-level webhooks (fired when a specific task completes).
This reference shows the actual structure produced by Tallyfy’s API code, not theoretical examples. If you’re building an integration, you’ll want to use these structures as your guide.
The event field
Section titled “The event field”Process-level and task-level payloads both carry a top-level event field that names what happened. One URL can receive several kinds of webhook and route each one by its event value.
event | Sent when | Payload |
|---|---|---|
process.launched | A process is launched | Process-level |
process.completed | A process is completed | Process-level |
task.completed | A task is completed | Task-level |
task.assigned | A task is assigned to a member, guest, or group | Task-level, plus assignees and assigned_by |
task.comment_added | Someone comments on a task | Task-level, plus comment |
task.issue_raised | Someone reports an issue on a task | Task-level, plus comment |
task.issue_resolved | Someone resolves an issue on a task | Task-level, plus comment |
Template webhooks send the first three, and step webhooks send task.completed. The other four go only to webhooks set up through Tallyfy’s watcher API, such as the organization-wide subscription a middleware connector registers for its instant triggers. Comment and issue events go only to organization-wide subscriptions.
Process-level webhook payload
Section titled “Process-level webhook payload”Process-level webhooks fire when someone launches a new process or when a process completes. They include the full process state and all kick-off form data.
Top-level structure
Section titled “Top-level structure”Process Webhook Payload├── Process information (from RunTransformer)│ ├── id, increment_id, name, summary│ ├── status, progress, whole_progress│ ├── checklist_id, checklist_title│ ├── created_at, started_at, completed_at│ ├── owner_id, started_by│ ├── collaborators (array of user IDs)│ └── Kick-off form data│ ├── prerun (map of field timeline ID => field value)│ ├── prerun_status, prerun_completed_at│ └── prerun_submitted_by│├── event│ └── process.launched or process.completed│├── process-tasks│ └── Map of task alias => task ID│├── ko-form-fields│ └── Map of field alias => field value│├── guest-links│ └── Map of guest email => {link_for_guest: URL}│└── launcher └── Person who launched the process (for process.completed, the person whose action completed it)Complete example
Section titled “Complete example”{ "id": "abc123def456ghi789jkl012mno345pq", "increment_id": 1234, "checklist_id": "template789xyz123abc", "checklist_title": "Vendor Contract Review Workflow", "name": "Vendor Contract Review - ACME Corp", "summary": "Complete vendor contract review and approval workflow", "status": "in-progress", "progress": 60, "whole_progress": 60, "started_by": "user123abc456", "owner_id": "user123abc456",
"prerun": { "a1b2c3d4e5f6a7b8c9d0e1f2a3b4c5d6": "Widget Suppliers Inc", "3c9d1e7fa4b820516d8e2f7a9c0b4d15": "Service Agreement", "9f2b7c1e4a6d8035b1c7e9d2f4a6b801": "50000", "6e4a2c80f19b3d75e0a8c246b93f157d": "normal" }, "prerun_status": "completed", "prerun_completed_at": "2025-10-01T09:15:00Z", "prerun_completed_by": "user123abc456", "prerun_length": 4,
"created_at": "2025-10-01T09:00:00Z", "started_at": "2025-10-01T09:15:00Z", "last_updated": "2025-10-02T14:30:00Z", "completed_at": null,
"collaborators": ["user123", "user456", "user789"], "late_tasks": 0, "due_soon": true, "due_date": "2025-10-15T17:00:00Z",
"users": [ {"user_id": "user123", "full_name": "John Smith"}, {"user_id": "user456", "full_name": "Jane Doe"} ], "groups": [ {"group_id": "group123", "name": "Legal Team"} ],
"event": "process.launched",
"process-tasks": { "initial-review": "task001abc", "legal-review": "task002def", "review-contract": "task003ghi", "execute-contract": "task004jkl" },
"ko-form-fields": { "vendor-name": "Widget Suppliers Inc", "contract-type": "Service Agreement", "estimated-value": "50000", "urgency": "normal" },
"guest-links": { "external.reviewer@vendor.com": { "link_for_guest": "https://go.tallyfy.com/guest-all-task/org789xyz123/guestcode123abc" }, "compliance@vendor.com": { "link_for_guest": "https://go.tallyfy.com/guest-all-task/org789xyz123/guestcode456def" } },
"launcher": { "id": "user123abc456", "email": "john@acmecorp.com", "full_name": "John Smith" }}Task-level webhook payload
Section titled “Task-level webhook payload”Task-level webhooks fire when a specific task completes. Watcher webhooks also send this payload for the other task events in the event field table. It contains detailed info about the task, all process data collected so far, and the next task that needs attention.
What’s next_task? It’s the first visible incomplete task ordered by position number. “Visible” means it wasn’t auto-skipped by an automation. “Incomplete” means it hasn’t been completed yet (includes tasks that are in-progress). Returns an empty array [] if all tasks in the process are done.
Top-level structure
Section titled “Top-level structure”Task Webhook Payload├── event│ └── task.completed, or another task event│├── this_task│ ├── id, title, alias, status, summary│ ├── deadline, position│ ├── completed_at, completed_by│ ├── owners (users array + guests array)│ ├── captures (form fields in THIS task)│ │ └── {timeline_id: {alias, label, value}}│ └── assets (files attached to the process, see below)│├── launcher│ └── Person whose action sent this webhook, such as│ the person who completed the task (not the│ person who launched the process)│├── completer│ └── Same person as launcher│├── process-tasks│ └── Map of task alias/id => task ID│├── form-fields│ └── ALL form fields from ALL tasks (flat structure)│ └── {field_alias: value}│├── template│ └── id, title, alias, summary (NO version field)│├── process│ ├── id, organization_id, name, summary│ ├── status, progress, created_at│ ├── ClientURL (link to process in Tallyfy)│ ├── prerun (kick-off form values)│ ├── process_forms (all fields by step_id)│ │ └── {step_id: {timeline_id: {alias, label, value}}}│ └── tags│├── guest-links│ └── Map of guest email => {link_for_guest: URL}│└── next_task ├── id, title, alias, status ├── summary, position, deadline ├── owners (users + guests) └── captures (empty until task is worked on)Complete example
Section titled “Complete example”{ "event": "task.completed",
"this_task": { "id": "task003ghi789xyz", "title": "Review Contract", "alias": "review-contract", "status": "completed", "summary": "Review the vendor contract for accuracy", "deadline": "2025-10-15T17:00:00Z", "position": 3, "completed_at": "2025-10-02T14:30:00Z", "completed_by": { "id": "user123", "email": "john@acmecorp.com", "full_name": "John Smith" }, "captures": { "capture123timeline": { "alias": "contract-value", "label": "Contract Value", "value": "50000" }, "capture456timeline": { "alias": "approval-notes", "label": "Approval Notes", "value": "All terms look good. Approved for signature." } }, "owners": { "users": [ { "id": "user123", "email": "john@acmecorp.com", "full_name": "John Smith" } ], "guests": [ "external.reviewer@vendor.com" ] }, "assets": [ { "id": "asset123", "filename": "signed-contract.pdf", "version": 1, "public_link": "https://go.tallyfy.com/api/organizations/org789xyz123/file/asset123/dl" } ] },
"launcher": { "id": "user123", "email": "john@acmecorp.com", "full_name": "John Smith" },
"completer": { "id": "user123", "email": "john@acmecorp.com", "full_name": "John Smith" },
"process-tasks": { "initial-review": "task001abc", "legal-review": "task002def", "review-contract": "task003ghi", "task004jkl": "task004jkl" },
"form-fields": { "initial-review-notes": "Contract appears standard. Needs legal review.", "risk-assessment": "Low", "legal-review-status": "Approved", "contract-value": "50000", "approval-notes": "All terms look good. Approved for signature.", "approval-date": "2025-10-02" },
"template": { "id": "template123abc", "title": "Vendor Contract Review Workflow", "alias": "vendor-contract-review", "summary": "Standard workflow for reviewing and approving vendor contracts" },
"process": { "id": "run123abc456", "organization_id": "org789xyz123", "name": "Vendor Contract Review - ACME Corp", "summary": "Complete vendor contract review and approval workflow", "status": "in-progress", "progress": 60, "created_at": "2025-10-01T09:00:00Z", "ClientURL": "https://go.tallyfy.com/tracker/run123abc456", "prerun": { "a1b2c3d4e5f6a7b8c9d0e1f2a3b4c5d6": "Widget Suppliers Inc", "3c9d1e7fa4b820516d8e2f7a9c0b4d15": "Service Agreement", "9f2b7c1e4a6d8035b1c7e9d2f4a6b801": "50000" }, "process_forms": { "step001": { "capture001timeline": { "alias": "initial-review-notes", "label": "Initial Review Notes", "value": "Contract appears standard." } }, "step002": { "capture002timeline": { "alias": "legal-review-status", "label": "Legal Review Status", "value": "Approved" } } }, "tags": [ { "id": "tag123", "name": "High Priority", "color": "#FF6B6B" } ] },
"guest-links": { "external.reviewer@vendor.com": { "link_for_guest": "https://go.tallyfy.com/guest-all-task/org789xyz123/guestcode123abc" }, "compliance@vendor.com": { "link_for_guest": "https://go.tallyfy.com/guest-all-task/org789xyz123/guestcode456def" } },
"next_task": { "id": "task004jkl", "title": "Execute Contract", "alias": "execute-contract", "status": "not-started", "summary": "Send final contract for execution", "position": 4, "deadline": "2025-10-20T17:00:00Z", "captures": {}, "owners": { "users": [ { "id": "user789", "email": "legal@acmecorp.com", "full_name": "Legal Team" } ], "guests": [] } }}Files and download links
Section titled “Files and download links”this_task.assets lists files attached to the process, so it can include files uploaded in other tasks, not only the task that sent the webhook. Each entry has the file’s id, filename, version (a whole number), and a public_link in this form:
https://go.tallyfy.com/api/organizations/{org_id}/file/{file_id}/dlDon’t treat assets as the full list of uploaded files. For that, read the file fields in form-fields: each file uploaded to Tallyfy appears there with its id and filename. Build the download link from that id, in the same form as above. The link is stable and doesn’t expire. To fetch a file, send the same headers as any other API call, as shown in Download file.
Request headers
Section titled “Request headers”Every webhook is an HTTP POST with a JSON body (Content-Type: application/json). Tallyfy also sends these headers:
| Header | What it carries |
|---|---|
X-Tallyfy-orgID | The ID of the Tallyfy organization that sent the webhook. It identifies the sender, but it isn’t a secret, so it doesn’t prove a request came from Tallyfy. |
X-Tallyfy-Idempotency-Key | A SHA-256 hash of the raw request body. The same body always gives the same key, so a retried delivery carries the key you’ve already seen and you can skip it. Tallyfy builds the body again for each attempt, so if the process changed between attempts, the retry carries a new key. |
X-Tallyfy-Timestamp | When Tallyfy signed the request, as a Unix timestamp in seconds. |
X-Tallyfy-Signature | A signature of the request body. Tallyfy doesn’t share the key behind it with customers today, so you can’t check it. |
Tallyfy adds the last two whenever it can sign the delivery.
Guest links
Section titled “Guest links”The guest-links object provides direct access URLs for all guest assignees in a process. You can use it to send personalized emails or SMS notifications to guests with direct links to their assigned tasks. There’s no need to generate separate links - they’re included automatically.
Structure
Section titled “Structure”"guest-links": { "guest.email@company.com": { "link_for_guest": "https://go.tallyfy.com/guest-all-task/{org_id}/{guest_code}" }}Key details
Section titled “Key details”- Included in both webhook types - process-level and task-level
- All unique guests - every unique guest assigned anywhere in the process
- Direct access - links bypass login, so guests click and see their tasks
- Persistent codes - guest codes stay stable across the process lifecycle
Common use cases
Section titled “Common use cases”- Custom email notifications - build your own notification system using Zapier or Make
- SMS integration - send task reminders via Twilio with direct access links
- CRM integration - update external CRM records with guest task links
- Client portals - embed guest task links in customer-facing dashboards
Form field structures
Section titled “Form field structures”Form fields appear in webhooks using different structures depending on context. Here’s how each one works.
In this_task.captures - detailed structure
Section titled “In this_task.captures - detailed structure”Task-specific form fields use the complete structure with timeline IDs as keys:
"captures": { "capture_timeline_123": { "alias": "contract-value", "label": "Contract Value", "value": "50000" }, "capture_timeline_456": { "alias": "approval-notes", "label": "Approval Notes", "value": "All terms look good." }}In form-fields - flat structure
Section titled “In form-fields - flat structure”All process form fields use a simplified flat structure with aliases as keys:
"form-fields": { "contract-value": "50000", "approval-notes": "All terms look good.", "initial-review-notes": "Contract appears standard.", "legal-review-status": "Approved"}In process.process_forms - organized by step
Section titled “In process.process_forms - organized by step”Form fields grouped by which step they belong to:
"process_forms": { "step001": { "capture_timeline_001": { "alias": "initial-review-notes", "label": "Initial Review Notes", "value": "Contract appears standard." } }, "step002": { "capture_timeline_002": { "alias": "legal-review-status", "label": "Legal Review Status", "value": "Approved" } }}Date field formatting
Section titled “Date field formatting”Date fields in captures respect your organization’s webhook date format setting. Tallyfy supports two formats:
ISO 8601 format (default):
"start-date": { "alias": "start-date", "label": "Contract Start Date", "value": "2025-10-15"}Localized format (if configured as d/m/Y h:i A T):
"start-date": { "alias": "start-date", "label": "Contract Start Date", "value": "15/10/2025 09:00 AM UTC"}This format applies to all date fields across the webhook payload. It’s consistent regardless of where the date field appears.
Task assignment info
Section titled “Task assignment info”Task owners appear in the owners object containing both organization members and external guests.
"owners": { "users": [ { "id": "user123", "email": "john@acmecorp.com", "full_name": "John Smith" }, { "id": "user456", "email": "jane@acmecorp.com", "full_name": "Jane Doe" } ], "guests": [ "external.reviewer@vendor.com", "compliance@vendor.com" ]}Important differences:
userscontains full objects with ID, email, and nameguestscontains only email addresses as strings- Use
guest-linksto get access URLs for guests
Common integration patterns
Section titled “Common integration patterns”Sending custom emails to guests
Section titled “Sending custom emails to guests”Use the guest-links object to send personalized emails:
// In your webhook handler (e.g., Zapier, Make, n8n)const guestLinks = webhookPayload['guest-links'];
for (const [email, linkData] of Object.entries(guestLinks)) { sendEmail({ to: email, subject: `New task assigned: ${webhookPayload.this_task.title}`, body: `You have a new task. Click here to access: ${linkData.link_for_guest}` });}Extracting all collected data
Section titled “Extracting all collected data”Get all form data collected across the entire process:
// Flat structure - simplest for most integrationsconst allFormData = webhookPayload['form-fields'];// Example: { "contract-value": "50000", "approval-notes": "..." }
// Or get kick-off form data specifically, keyed by field timeline IDconst kickoffData = webhookPayload.process.prerun;// Example: { "a1b2c3d4e5f6a7b8c9d0e1f2a3b4c5d6": "Widget Suppliers Inc", ... }Kick-off answers show up in form-fields as well, under their alias. Reach for prerun when you want them on their own, and remember its keys are timeline IDs rather than the readable names you see in form-fields.
Triggering follow-up actions
Section titled “Triggering follow-up actions”Use the next_task object to prepare for upcoming steps:
const nextTask = webhookPayload.next_task;
// next_task is an empty array [] when all tasks are doneif (nextTask && nextTask.title === "Execute Contract") { // Prepare contract execution system // Notify legal team // Update external tracking system}Related articles
Section titled “Related articles”Webhooks > Send emails using webhooks