MediumAgentsPython 3

Webhook Idempotency Receiver

Handle retried provider events with delivery dedupe, logical idempotency keys, and repo policy checks.

35m4 sample tests9 hidden tests

Implement WebhookReceiver, an in-memory receiver for provider events that may be retried.

Requirements

  • Constructor receives allowed_repos, a set of "org/repo" strings.
  • handle(provider, delivery_id, event) returns a response dictionary.
  • Check order per delivery: if (provider, delivery_id) was already handled, return the cached response immediately (even if the body changed). Otherwise validate fields, then repo policy, then logical run creation.
  • A duplicate (provider, delivery_id) returns the original response (including bad_request and blocked).
  • Events must contain non-empty org, repo, and action (empty strings count as missing).
  • If org, repo, or action is missing or empty, return {"status": "bad_request", "code": 400} and cache that response under the delivery key (like any other response).
  • If "org/repo" isn't allowed, return {"status": "blocked", "code": 403} and cache it under the delivery key.
  • For allowed events, create exactly one run for each logical key.
  • The logical key is provider:org/repo:action:number, where number is optional; missing or null defaults to the empty string (use event.get("number") or "").
  • Return {"status": "created", "code": 202, "run_id": ...} for new runs and {"status": "duplicate", "code": 200, "run_id": ...} for duplicate logical work.

Example

python
1receiver = WebhookReceiver({"cursor/app"}) 2event = {"org": "cursor", "repo": "app", "action": "issue_comment", "number": 7} 3 4first = receiver.handle("github", "delivery-1", event) 5second = receiver.handle("github", "delivery-2", event) 6 7assert first["status"] == "created" 8assert second["status"] == "duplicate" 9assert first["run_id"] == second["run_id"] 10 11assert receiver.handle("github", "d-bad", {"repo": "app", "action": "push"}) == { 12 "status": "bad_request", 13 "code": 400, 14}

Constraints

  • Keep it in memory.
  • Don't parse provider-specific payloads.
  • Preserve deterministic run IDs: run-1, run-2, and so on.

Editor