Idempotency

CueAPI uses at-least-once delivery. Here's how to handle duplicate deliveries.

At-least-once delivery

CueAPI guarantees at-least-once delivery. This means your handler may receive the same execution more than once in rare edge cases:

  • Network timeout during delivery (CueAPI retries, but your handler already processed it)
  • Worker crashes after processing but before reporting the outcome
  • Infrastructure failover during delivery

This is a deliberate design choice. At-least-once is more reliable than at-most-once because it never silently drops executions.

Why not exactly-once?

Exactly-once delivery across distributed systems is extremely difficult to achieve without significant performance and complexity tradeoffs. Most production systems (Stripe webhooks, AWS SQS, Google Cloud Pub/Sub) use at-least-once delivery and recommend idempotent handlers.

Making your handler idempotent

An idempotent handler produces the same result whether it runs once or multiple times for the same execution.

Strategy 1: Track execution IDs

Store processed execution IDs and skip duplicates:

python
# Python / Flask example
PROCESSED = set()  # Use Redis or a database in production
 
@app.route("/webhook", methods=["POST"])
def handle_webhook():
    execution_id = request.headers.get("X-CueAPI-Execution-Id")
 
    if execution_id in PROCESSED:
        return {"status": "already processed"}, 200
 
    # Process the execution
    result = do_work(request.json)
 
    PROCESSED.add(execution_id)
    return {"status": "ok"}, 200

Strategy 2: Database upserts

Use INSERT ... ON CONFLICT DO NOTHING for operations that create records:

sql
INSERT INTO reports (execution_id, content, created_at)
VALUES ($1, $2, NOW())
ON CONFLICT (execution_id) DO NOTHING;

Strategy 3: Check-before-act

Before performing a side effect, check if it's already been done:

python
def handle_execution(payload, execution_id):
    # Check if already processed
    if db.query("SELECT 1 FROM processed WHERE id = %s", execution_id):
        return "already done"
 
    # Do the work
    send_email(payload["recipient"], payload["message"])
 
    # Mark as processed
    db.execute("INSERT INTO processed (id) VALUES (%s)", execution_id)

Execution ID as idempotency key

The X-CueAPI-Execution-Id header (for webhooks) or the execution ID from the claim response (for workers) is your idempotency key. It's unique per execution and stable across retries.

Note

Retries of the same execution use the same execution ID. Different scheduled fires of the same cue produce different execution IDs.

Worker transport idempotency

For worker transport, the claim mechanism provides built-in protection: only one worker can claim an execution. However, if your handler crashes after completing work but before the outcome is reported, the execution may be reclaimed. Use the same idempotency strategies above.