Handling idempotency
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.
Someone completes a task in Tallyfy, then reopens it and completes it again. Your webhook fires both times - same task, different timestamps.
- User completes the task
- Your webhook receives the event
- User reopens the task, then completes it again
- Your webhook receives another event for the same task
Without deduplication, this creates duplicate records, repeated emails, and double-processed payments.
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.
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.
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.
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:
- Build a unique event ID from
task_id + event_type + timestamp - Check if that ID exists in your table
- If new, process it and store the ID
- If it exists, log and skip
When duplicate API requests arrive, don’t throw errors - work with them:
-
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.
-
Use conditional updates - Before updating form fields through the API, check the current value first. If it already matches, skip the update.
-
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.
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
A process with many tasks generates many webhooks. To handle the volume:
- Batch processing - Collect events and process in chunks every few minutes instead of one at a time
- Use queues - Push webhook events into a message queue so your system doesn’t get overwhelmed
- Filter by task - Check the payload’s
this_task.aliasorthis_task.positionand only process the tasks you care about
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
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' };}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;}- Simulate duplicate webhooks - Fire the same payload at your endpoint 3-4 times in a row and confirm only one record is created
- Test network retries - Use Postman or curl to simulate timeouts and verify retries don’t create duplicates
- Check data consistency - After tests, verify no corrupted or duplicated data
- Monitor production logs - Watch for duplicate patterns once live
| Issue | Cause | Solution |
|---|---|---|
| Duplicate records in database | No check before insert | Add unique constraints and check before inserting |
| Missing webhook events | Treating duplicates as errors | Log duplicates but always return a 2xx response |
| Inconsistent data state | Events processed out of order | Use timestamps for correct ordering |
| Rate limits from retries | Not caching successful responses | Cache 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.
- Monitor logs for duplicate patterns - they’ll reveal retry behaviors you didn’t expect
- Tune your deduplication window based on real-world data
- For full audit trails, consider event sourcing
- Stay current with Tallyfy’s webhook documentation - payload formats can evolve
Webhooks > Details about webhooks
Was this helpful?
- 2025 Tallyfy, Inc.
- Privacy Policy
- Terms of Use
- Report Issue
- Trademarks