Webhooks > Details about webhooks
Handling idempotency
Idempotency ensures your integrations work correctly when the same thing happens twice. Think of it like pressing an elevator button - no matter how many times you press it, the elevator only comes once. That’s what you want when building integrations with Tallyfy’s webhooks and API.
Here’s the reality: webhooks can fire multiple times for the same event. External systems might send duplicate API requests. Network glitches happen. Without proper idempotency handling, you’ll face a mess - duplicate records in your database, customers getting charged twice, or inventory counts going haywire.
Let’s say someone completes a task in Tallyfy. Your webhook gets the “task completed” event - great! But what if they reopen that task and complete it again? You’ll get another webhook for the exact same task.
It happens more than you’d think:
- User completes the task
- Your webhook receives the event
- User realizes they forgot something, reopens the task
- User completes it again
- Your webhook receives another event - same task, different timestamp
Without idempotency handling, you’re looking at chaos:
- Duplicate records cluttering your database
- Customers getting three confirmation emails for one action
- Payment systems processing the same order twice (yikes!)
- Inventory numbers that don’t match reality
Set up a webhook at the process level, and you’re in for a surprise - it fires for every single task completion in that process. Got 10 tasks? That’s 10 webhook events coming your way.
The math gets scary fast:
- 10-task process = 10 webhook events minimum
- Tasks get reopened and recompleted? More events pile up
- Your system better be ready for the flood
It’s not just Tallyfy that can send duplicates - your own systems can too. We’ve seen it all:
- Helpdesk software hiccups and sends the same ticket update twice
- Network timeout triggers an automatic retry, but the first request actually went through
- Someone double-clicks a button (we’ve all been there) and fires off multiple API calls
Tallyfy gives you everything you need to catch duplicates - unique IDs in every webhook payload. Use them!
{ "event": "task.completed", "task_id": "abc123", "process_id": "xyz789", "completed_at": "2024-01-15T10:30:00Z", "completed_by": "user@example.com"}
Your game plan:
- Store the
task_id
andcompleted_at
combo in your database - Check this combination before processing any webhook
- Already seen it? Skip or update the existing record
- Brand new? Process away
You need a dedicated place to track what you’ve already processed. A simple table works wonders:
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:
task_id + event_type + timestamp
- Check your table - have you seen this ID before?
- New event? Process it and save that ID
- Old news? Log it and move on
Here’s where things get interesting. When duplicate API requests come in, don’t fight them - work with them:
-
Return success for duplicate requests: External system tries to create the same record twice? Don’t throw an error. Send back a 200 OK with the existing record. Everyone’s happy.
-
Use conditional updates: Before updating form fields through the API, check what’s already there:
- Value already matches? Skip the update
- Different value? Go ahead and update
- Always add a comment for the audit trail
-
Use request IDs: Make external systems include a unique ID with each call:
X-Request-ID: unique-request-identifier-123Keep these IDs for 24 hours. When you see a repeat, you’ll know it’s a retry.
Tasks get reopened and recompleted all the time. You’ve got to track the full history:
-
Keep a record of every completion:
{"task_id": "abc123","completions": [{"completed_at": "2024-01-15T10:30:00Z", "completed_by": "user1@example.com"},{"completed_at": "2024-01-15T14:45:00Z", "completed_by": "user2@example.com"}]} -
Pick your strategy based on what makes sense:
- Care only about the first completion? Ignore the rest
- Need to track every completion? Store them all separately
- Only the latest matters? Update your records each time
Got a process with 50 tasks? That’s 50 webhooks headed your way. Here’s how to handle the deluge:
- Batch processing: Don’t process events one by one. Collect them and process in chunks every 5 minutes
- Use queues: Message queues are your friend - they’ll prevent your system from choking on the volume
- Filter smartly: Not all tasks are equal. Check the payload and process only what matters to you
Stop duplicates before they start when external systems talk to Tallyfy:
-
Use idempotency keys: Every operation needs its own unique key:
POST /api/processes/launchX-Idempotency-Key: ticket-12345-launch-attempt-1 -
Check before you leap: Always verify the current state first:
- About to launch a process? Check if it already exists
- Completing a task? Make sure it’s not already done
- Updating a form field? Confirm it needs changing
async function processWebhook(payload) { // Generate unique event key const eventKey = `${payload.task_id}-${payload.event}-${payload.timestamp}`;
// Check if already processed const existing = await db.query('SELECT * FROM processed_events WHERE event_key = ?', [eventKey]);
if (existing.length > 0) { console.log('Duplicate event detected, skipping:', eventKey); return { status: 'duplicate', message: 'Event already processed' }; }
// Process the event await handleEvent(payload);
// Mark as processed await db.query('INSERT INTO processed_events (event_key, processed_at) VALUES (?, NOW())', [eventKey]);
return { status: 'processed', message: 'Event processed successfully' };}
async function updateTaskField(taskId, fieldName, fieldValue, requestId) { // Check if this request was already processed const cachedResult = await cache.get(`request:${requestId}`); if (cachedResult) { return cachedResult; }
// Get current task state const task = await tallyfyApi.getTask(taskId);
// Check if update is needed if (task.fields[fieldName] === fieldValue) { const result = { status: 'unchanged', message: 'Field already has the desired value' }; await cache.set(`request:${requestId}`, result, 86400); // Cache for 24 hours return result; }
// Perform update const updatedTask = await tallyfyApi.updateTask(taskId, { fields: { [fieldName]: fieldValue } });
// Add comment for audit trail await tallyfyApi.addComment(taskId, `Field "${fieldName}" updated to "${fieldValue}" via API integration` );
const result = { status: 'updated', task: updatedTask }; await cache.set(`request:${requestId}`, result, 86400); return result;}
Don’t wait for production to find out if your deduplication works. Test it now:
- Simulate duplicate webhooks: Fire the same webhook at your system 3-4 times in a row
- Test network retries: Use tools like Postman to simulate connection timeouts and automatic retries
- Check data consistency: After your tests, verify your data isn’t corrupted or duplicated
- Monitor production logs: Watch for duplicate patterns - they’ll show up eventually
Issue | Cause | Solution |
---|---|---|
Duplicate records in database | Not checking for existing records before insert | Implement unique constraints and check before insert |
Missing webhook events | Treating duplicates as errors | Log duplicates but don’t fail the webhook response |
Inconsistent data state | Processing events out of order | Use timestamps to ensure correct ordering |
API rate limits from retries | Not caching successful responses | Implement response caching with appropriate TTL |
Important consideration
Always respond with a 2xx status code to webhook requests, even for duplicates. If you return an error, Tallyfy thinks something went wrong and retries - creating even more duplicates. Don’t make things worse!
You’ve built idempotency into your integration - now keep it running smoothly:
- Monitor your logs for duplicate patterns (they’ll reveal retry behaviors you didn’t expect)
- Fine-tune your deduplication window based on real-world data
- Need complete audit trails? Consider event sourcing for the full picture
- Stay current with Tallyfy’s webhook documentation - payload formats can evolve
Open Api > Integrate with Tallyfy using the API
- 2025 Tallyfy, Inc.
- Privacy Policy
- Terms of Use
- Report Issue
- Trademarks