Skip to content

R1.2 Webhook task hardening - #9

Merged
alexeygrigorev merged 1 commit into
mainfrom
issue-6-webhook-task-hardening
Sep 7, 2026
Merged

alexeygrigorev merged 1 commit into
mainfrom
issue-6-webhook-task-hardening

Conversation

@alexeygrigorev

Copy link
Copy Markdown
Member

Implements R1.2 (webhook task hardening) from the community-base unification plan. Closes #6.

What changed

  1. Timeout ceiling decided and documented: 60 seconds synchronous. Ack-then-callback mode added: a receiver can answer 202 with {"lease_seconds": N}; Relay keeps the task running under a lease and fails it when the lease expires without a completion. New client callbacks POST /api/tasks/{id}/complete and POST /api/tasks/{id}/fail (client-scoped, idempotent, conflict-aware). Lease cap 3600s (RELAY_WEBHOOK_MAX_LEASE_SECONDS).
  2. Retry classes: transport timeouts, 429 and 5xx retry with exponential backoff (5s base) up to per-task max_attempts, now defaulting to 5. Other 4xx (including 408, 425) fail immediately. Response bodies stored on the task are truncated to 2 KB (was 4 KB).
  3. Per-client webhook limits: 4 in-flight default (RELAY_WEBHOOK_MAX_INFLIGHT_PER_CLIENT) and a 60-per-minute creation rate (RELAY_WEBHOOK_RATE_LIMIT_PER_MINUTE), both rejected with 429. Schedule-created tasks are exempt (operator-configured, cron-bounded).
  4. X-Relay-Attempt header added (1-based); X-Relay-Task-Id, X-Relay-Correlation-Id, X-Relay-Timestamp, X-Relay-Signature unchanged.
  5. Dead-letter list at /jobs/dead-letters/ (staff-gated): failed tasks with attempts, response, error, and a retry button that requeues with attempts reset.
  6. Tests for every rule, including replay of an old timestamp rejected by the reference receiver now documented in docs/api.md.
  7. docs/api.md documents the 202 lease protocol, the retry table, per-client limits and the reference receiver; docs/context.md open decision 1 is resolved; README updated.

Verification

uv run pytest tests -k webhook -> pass:

43 passed, 62 deselected in 19.81s

Full suite -> pass (562 passed in 265.63s); uv run ruff check . -> pass; manage.py check and makemigrations --check --dry-run -> clean.

Live receiver scenarios (local HTTPS receiver with a self-signed cert and test database; no credentials, no shared infrastructure touched):

  • Receiver sleeping 70s with timeout_seconds: 60, max_attempts: 1 -> failed after 60.1s, error webhook request failed: The read operation timed out.
  • Receiver answering 202 {"lease_seconds": 300} and completing after 90s -> running under lease, then POST /api/tasks/{id}/complete -> 200, task succeeded, result stored.

Sandbox-native runs of the same scenarios are not included: they need this branch deployed to the sandbox and a registered sleeping receiver on a client origin. The deployment happens through the normal push-to-main release; the checks should be repeated there afterwards.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

R1.2 Webhook task hardening

1 participant