Webhook endpoint in integration hub sends duplicate events when retry is triggered

Integration hub webhooks are resending events multiple times when the receiving endpoint experiences temporary failures. We have idempotency implementation on our side, but we’re still seeing duplicate processing because the event payload structure changes slightly on retries.

The webhook retry logic triggers after a 5xx error from our endpoint, which is correct behavior. However, each retry includes a different timestamp in the payload, causing our idempotency check (based on payload hash) to fail.

Example event payload on first send:


{"eventId":"abc123","eventType":"ContactCreated",
"timestamp":"2025-02-18T10:15:30Z"}

On retry (after 30 seconds):


{"eventId":"abc123","eventType":"ContactCreated",
"timestamp":"2025-02-18T10:16:00Z"}

Is there a way to configure webhook retry logic to maintain identical payload structure, or should we implement idempotency differently?

That sounds like you’re capturing events that fire during the same transaction. If multiple field updates happen rapidly, Creatio might batch them into a single event, but if the webhook fires before all updates complete, you get partial data. Then the retry includes the complete data. Check your webhook trigger configuration - is it set to fire on field change or on record save?

Your issue requires a multi-layered solution addressing webhook retry logic, idempotency implementation, and event payload structure. Here’s the comprehensive fix:

Understanding the Problem: Creatio 7.18’s webhook system generates timestamps at send-time, not event-time. This is intentional - it helps receiving systems track retry attempts and implement age-based filtering. However, it complicates hash-based idempotency.

Proper Idempotency Implementation:

  1. Use eventId as Primary Key: Never use payload hash for idempotency with webhooks. The eventId is specifically designed for this purpose and remains constant across all retries:

// Your idempotency check should be:
SELECT * FROM processed_events
WHERE event_id = 'abc123'

If the record exists, skip processing regardless of other payload differences.

  1. Webhook Retry Logic Configuration: Modify your integration hub webhook settings to optimize retry behavior:
  • MaxRetryAttempts: 3 (default is fine)
  • InitialRetryDelay: 60 seconds (increase from default 30)
  • RetryMultiplier: 2 (exponential backoff)
  • MaxRetryDelay: 300 seconds

This gives your endpoint more recovery time between retries.

  1. Event Payload Structure Standardization: The payload variance you’re seeing (different field sets on retry) indicates timing issues. Configure your webhook trigger properly:

In Integration Hub, for your webhook configuration:

  • Trigger Type: “On Record Saved” (not “On Field Changed”)
  • Include Delay: 2 seconds (allows transaction to complete)
  • Payload Mode: “Complete Record” (ensures all fields included)

This ensures the payload is consistent from the first send.

Advanced Idempotency Pattern:

For robust duplicate prevention, implement this pattern:


// Pseudocode - Two-tier idempotency:
1. Check if eventId exists in processed_events table
2. If not exists, check if similar event processed recently:
   - Same entity (Contact)
   - Same record ID
   - Same eventType (Created/Updated)
   - Within last 60 seconds
3. If both checks pass, process event and store eventId
4. If either check fails, log duplicate and skip

This catches both exact duplicates (same eventId) and functional duplicates (rapid-fire events for same record).

Handling Partial vs Complete Payloads:

The issue where retries contain different data suggests webhook firing mid-transaction. Solutions:

  • Enable “WaitForTransactionComplete” in webhook config (if available in your version)
  • Implement eventual consistency on your side: Accept the first event, then update with data from retries if eventId matches but payload is more complete
  • Use webhook signature verification (as mentioned) to detect legitimate retries vs duplicate triggers

Webhook Signature Verification:

Creatio includes an X-Webhook-Signature header. Use it:


// Signature verification
expectedSignature = HMAC_SHA256(payload, webhookSecret)
if (receivedSignature == expectedSignature) {
  // Valid webhook from Creatio
}

The signature is calculated on the canonical payload (excluding timestamp), so it remains constant across retries. You can use this for idempotency instead of eventId if you prefer.

Monitoring and Debugging:

Add these checks to identify duplicate sources:

  • Log eventId, timestamp, and payload hash for every received webhook
  • Track retry attempts using the X-Retry-Count header (if present)
  • Monitor for eventIds appearing with different payloads - this indicates configuration issues

After implementing these changes, your system should handle retries gracefully without duplicate processing, even when payloads vary slightly due to timing.

The timestamp change is by design - it represents when the webhook was sent, not when the event occurred. For idempotency, you should use the eventId field only, not the entire payload hash. The eventId remains constant across retries and uniquely identifies the business event.

In addition to using eventId for idempotency, check if your integration hub configuration has the retry count set appropriately. By default, Creatio retries 3 times with exponential backoff. If your endpoint has intermittent issues, you might want to increase the initial retry delay to give your system more time to recover before the first retry.