Skip to content
Developers

Webhook payload structure

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.

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.

eventSent whenPayload
process.launchedA process is launchedProcess-level
process.completedA process is completedProcess-level
task.completedA task is completedTask-level
task.assignedA task is assigned to a member, guest, or groupTask-level, plus assignees and assigned_by
task.comment_addedSomeone comments on a taskTask-level, plus comment
task.issue_raisedSomeone reports an issue on a taskTask-level, plus comment
task.issue_resolvedSomeone resolves an issue on a taskTask-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 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.

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)
{
"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 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.

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)
{
"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": []
}
}
}

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}/dl

Don’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.

Every webhook is an HTTP POST with a JSON body (Content-Type: application/json). Tallyfy also sends these headers:

HeaderWhat it carries
X-Tallyfy-orgIDThe 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-KeyA 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-TimestampWhen Tallyfy signed the request, as a Unix timestamp in seconds.
X-Tallyfy-SignatureA 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.

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.

"guest-links": {
"guest.email@company.com": {
"link_for_guest": "https://go.tallyfy.com/guest-all-task/{org_id}/{guest_code}"
}
}
  • 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
  • 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 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."
}
}

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 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 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:

  • users contains full objects with ID, email, and name
  • guests contains only email addresses as strings
  • Use guest-links to get access URLs for 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}`
});
}

Get all form data collected across the entire process:

// Flat structure - simplest for most integrations
const allFormData = webhookPayload['form-fields'];
// Example: { "contract-value": "50000", "approval-notes": "..." }
// Or get kick-off form data specifically, keyed by field timeline ID
const 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.

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 done
if (nextTask && nextTask.title === "Execute Contract") {
// Prepare contract execution system
// Notify legal team
// Update external tracking system
}

Integrations > Webhooks

Tallyfy webhooks automatically send structured JSON data to any external system via HTTP POST…