Skip to content

Handling idempotency

What is idempotency and why it matters

Idempotency means your integration handles the same event twice without breaking anything. Like pressing an elevator button multiple times - the elevator still only comes once.

This matters because Tallyfy webhooks can fire more than once for the same logical event. External systems might retry API requests. Network issues happen. Without idempotency handling, you’ll end up with duplicate database records, double-sent emails, or repeated charges.

Common scenarios requiring idempotency

Task reopened and recompleted

Someone completes a task in Tallyfy, then reopens it and completes it again. Your webhook fires both times - same task, different timestamps.

  1. User completes the task
  2. Your webhook receives the event
  3. User reopens the task, then completes it again
  4. Your webhook receives another event for the same task

Without deduplication, this creates duplicate records, repeated emails, and double-processed payments.

Template-level webhooks fire per task

When you set a webhook URL on a template, it fires on every task completion in any process launched from that template - not just once per process. A 10-task process means 10 webhook events. The same webhook URL also fires when the process is launched.

External systems sending duplicates

Your own systems can cause duplicates too - a helpdesk retries a failed request that actually succeeded, or a user double-clicks a button that fires multiple API calls.

Implementing idempotency strategies

Use unique identifiers from the payload

Tallyfy webhook payloads include unique IDs you can use for deduplication. A task completion webhook contains a nested structure with task details:

{
"this_task": {
"id": "abc123",
"title": "Review document",
"alias": "review_doc",
"status": "completed",
"completed_at": "2024-01-15T10:30:00Z",
"completed_by": { "email": "user@example.com" },
"captures": { ... }
},
"process": {
"id": "xyz789",
"name": "Document Review",
"status": "active"
}
}

Use the task id combined with completed_at as your deduplication key. Check this combination before processing, and skip if you’ve seen it before.

Build an event deduplication table

Track processed events in a dedicated table:

CREATE TABLE processed_events (
event_id VARCHAR(255) PRIMARY KEY,
event_type VARCHAR(100),
processed_at TIMESTAMP,
payload JSON
);

Each time a webhook arrives:

  1. Build a unique event ID from task_id + event_type + timestamp
  2. Check if that ID exists in your table
  3. If new, process it and store the ID
  4. If it exists, log and skip

Handle duplicates gracefully

When duplicate API requests arrive, don’t throw errors - work with them:

  1. Return success for duplicates - If an external system tries to create a record that already exists, return a 200 OK with the existing record instead of an error.

  2. Use conditional updates - Before updating form fields through the API, check the current value first. If it already matches, skip the update.

  3. Track request IDs on your side - Have external systems include a unique ID with each call and cache results for 24 hours. When you see a repeated ID, return the cached response.

Best practices for specific integrations

Task completion webhooks

Tasks can be reopened and recompleted. Decide your strategy up front:

  • First completion only - Ignore subsequent completions for the same task ID
  • Track all completions - Store each completion separately with its timestamp
  • Latest wins - Update your records each time, keeping only the most recent completion

Managing template-level webhook volume

A process with many tasks generates many webhooks. To handle the volume:

  1. Batch processing - Collect events and process in chunks every few minutes instead of one at a time
  2. Use queues - Push webhook events into a message queue so your system doesn’t get overwhelmed
  3. Filter by task - Check the payload’s this_task.alias or this_task.position and only process the tasks you care about

Preventing duplicate API calls to Tallyfy

Before sending write requests to Tallyfy’s API, check the current state first:

  • About to launch a process? Query whether one already exists for this trigger
  • Completing a task? Verify it isn’t already completed
  • Updating a form field? Confirm the current value differs from what you’re sending

Example implementation patterns

Pattern 1 - Webhook processor with deduplication

async function processWebhook(payload) {
const task = payload.this_task;
const eventKey = `${task.id}-completed-${task.completed_at}`;
const existing = await db.query(
'SELECT * FROM processed_events WHERE event_id = ?', [eventKey]
);
if (existing.length > 0) {
console.log('Duplicate event, skipping:', eventKey);
return { status: 'duplicate' };
}
await handleEvent(payload);
await db.query(
'INSERT INTO processed_events (event_id, processed_at) VALUES (?, NOW())',
[eventKey]
);
return { status: 'processed' };
}

Pattern 2 - API integration with retry safety

async function updateTaskField(taskId, fieldName, fieldValue, requestId) {
const cachedResult = await cache.get(`request:${requestId}`);
if (cachedResult) return cachedResult;
const task = await tallyfyApi.getTask(taskId);
if (task.fields[fieldName] === fieldValue) {
const result = { status: 'unchanged' };
await cache.set(`request:${requestId}`, result, 86400);
return result;
}
const updatedTask = await tallyfyApi.updateTask(taskId, {
fields: { [fieldName]: fieldValue }
});
const result = { status: 'updated', task: updatedTask };
await cache.set(`request:${requestId}`, result, 86400);
return result;
}

Testing your idempotency implementation

  1. Simulate duplicate webhooks - Fire the same payload at your endpoint 3-4 times in a row and confirm only one record is created
  2. Test network retries - Use Postman or curl to simulate timeouts and verify retries don’t create duplicates
  3. Check data consistency - After tests, verify no corrupted or duplicated data
  4. Monitor production logs - Watch for duplicate patterns once live

Troubleshooting common issues

IssueCauseSolution
Duplicate records in databaseNo check before insertAdd unique constraints and check before inserting
Missing webhook eventsTreating duplicates as errorsLog duplicates but always return a 2xx response
Inconsistent data stateEvents processed out of orderUse timestamps for correct ordering
Rate limits from retriesNot caching successful responsesCache responses with an appropriate TTL

Important

Always respond with a 2xx status code to Tallyfy webhook requests, even for duplicates. Returning an error code could cause issues with delivery tracking. A 200 response with a “duplicate” message in the body is the safest approach.

Next steps

  1. Monitor logs for duplicate patterns - they’ll reveal retry behaviors you didn’t expect
  2. Tune your deduplication window based on real-world data
  3. For full audit trails, consider event sourcing
  4. Stay current with Tallyfy’s webhook documentation - payload formats can evolve

Webhooks > Webhook scenarios

Tallyfy webhooks automatically send JSON data to external systems when workflow events occur including process launches and task completions with the payload containing all accumulated data from the process up to that point enabling real-time integrations through middleware tools like Zapier.

Webhooks > Details about webhooks

Tallyfy webhooks send JSON data to external URLs when workflow events occur - at template level (any step completion or process launch/completion) or step level (specific task completions) - with an organization ID header for security validation.

Integrations > Webhooks

Tallyfy webhooks automatically send JSON data to external systems when workflow events occur enabling instant integration with middleware platforms custom APIs and serverless functions without requiring constant polling.

Pro > Integrations

Tallyfy offers twelve distinct ways to connect with your existing business software — ranging from a full REST API and webhooks for developers to no-code middleware platforms like Zapier and Make for non-technical users — along with email integration and chat tools like Slack and upcoming BYO AI capabilities so every team can automate data sharing and eliminate manual copy-pasting between systems regardless of technical skill level.