OpenRouter video webhook idempotency key vs Sume job_id

OpenRouter's video webhook sends X-OpenRouter-Idempotency-Key as job_id-status. Sume says use job_id alone. How to dedupe both, with a runnable verifier.

5 min readSume
All posts

OpenRouter's video webhooks carry an X-OpenRouter-Idempotency-Key header whose value is <job_id>-<status>, so a completed and a failed event for the same job have different keys. Sume sends no such header: its docs tell you to use job_id as the idempotency key on your side, and each job reaches one terminal event, so one key per job is enough. If you are porting a receiver, change the dedupe key and keep the rest.

OpenRouter's side is from its video generation guide, read on 2026-10-03, which also lists the events video.generation.completed, failed, cancelled, and expired and an optional X-OpenRouter-Signature HMAC-SHA256. Sume's side is from its webhooks page.

Why would the status be part of the key?

OpenRouter has four event types per job, and a job can in principle be reported more than once as its state changes, so a key made of job and status identifies one transition. A receiver that dedupes on job id alone would drop a later event for the same job.

Why is job_id enough on Sume?

Sume sends terminal job events only: job.completed, job.failed, and job.canceled. There are no progress or partial deliveries. A job reaches one terminal state, so the same job_id arriving again is a retry or a redeliver of the same event.

Retries happen until up to 10 attempts are used, with a fixed delay (30 seconds by default) and a 10-second timeout per attempt. Redeliver, from the dashboard or POST /v1/jobs/{job_id}/webhook/redeliver, re-sends the real terminal event with a fresh timestamp and signature, so you will see the same job_id again by design.

Dedupe keys for video job webhooks (read 2026-10-03)
ItemOpenRouterSume
Idempotency headerX-OpenRouter-Idempotency-Key, job_id-statusNone; use job_id from the body
Eventsvideo.generation.completed, failed, cancelled, expiredjob.completed, job.failed, job.canceled
Signature headerX-OpenRouter-Signature (optional), HMAC-SHA256x-sume-webhook-signature, sume-v1=hex
Timestamp headerNot listed on the page we fetchedx-sume-webhook-timestamp

How do you verify and dedupe a Sume delivery?

Verify first, then dedupe. Sume signs <timestamp>.<raw_body> with HMAC SHA-256, and the header may carry two sume-v1= entries during a secret rotation, so accept any match. Refuse an empty secret instead of treating it as a valid key. This function does both, with an in-memory set standing in for your database:

import hashlib, hmac, json, time

SEEN: set[str] = set()

def accept(secret: str, ts: str, sig_header: str, raw: bytes, tol: int = 300):
    if not secret:
        raise ValueError("empty signing secret")
    if abs(time.time() - int(ts)) > tol:
        return None
    mac = hmac.new(secret.encode(), f"{ts}.".encode() + raw, hashlib.sha256)
    want = "sume-v1=" + mac.hexdigest()
    ok = False
    for part in sig_header.split(","):
        ok |= hmac.compare_digest(part.strip(), want)
    if not ok:
        return None
    event = json.loads(raw)
    if event["job_id"] in SEEN:
        return None
    SEEN.add(event["job_id"])
    return event

What breaks when you port an OpenRouter receiver?

Two things. If your code reads X-OpenRouter-Idempotency-Key, it will find nothing on Sume. And if you key on job_id-status, you can still do it on Sume by building the string from job_id and the event field, but it adds nothing because one job has one terminal event.

Store the event durably before you return a 2xx, as the Sume docs advise, and keep status polling in place for deliveries that never arrive.

What does Sume not do?

It does not send a dedupe header and it does not send non-terminal events, so you cannot use webhooks for progress. For progress, poll the job. Send test deliveries to confirm your verifier before you point it at paid jobs.

How do you test your receiver before real jobs arrive?

Sume has two tools for this. Send test posts a dummy signed webhook.test payload to a URL you type; it carries no job_id, so your dedupe code must not crash when it is missing. Redeliver re-sends a real job's terminal event with a fresh timestamp and signature, which is the right way to prove your dedupe works, since the second delivery must be acknowledged but not processed twice.

Write both cases as tests: one delivery with no job_id gets a 2xx and is ignored, and the same job_id twice produces one side effect.

What belongs in the database?

Persist the event before you return success. Sume's docs say to return any 2xx after durably storing the event, and that non-2xx responses and network errors are retried up to ten attempts. Use job_id as a unique key in the table, insert first, and treat a unique-violation as 'already seen'. An in-memory set, like the one above, loses state on restart, which is the moment retries are most likely.

Keep status polling alive next to the webhook. A failed delivery does not mean a failed job.

Sources

Related posts

More in Comparisons

All Comparisons posts

Written by Sume