Nobody has started this yet — be first.
Business impact
Every job with a callback_url gets its result delivered there, HMAC-signed, specifically so a receiver can trust the payload wasn't forged or tampered with in transit. Right now the signature sent in X-Signature is computed over a different serialization of the payload than the bytes actually transmitted. Any receiver correctly implementing "verify the signature against the raw body you received" -- the only correct way to verify an HMAC -- will reject every single callback this service ever sends as unauthentic. This isn't a one-in-a-while flake -- it's a deterministic 100% failure rate for every integration built against this feature, the kind of bug that would only surface once a real client starts actually checking signatures instead of trusting the callback blindly.
Problem
body (compact separators, no spaces) is what's actually sent as the request content. The signature is computed over a second, independent json.dumps(payload) call using default separators (with spaces) -- a different byte sequence for the same logical dict.
Current behavior
deliver() builds the sent request body as json.dumps(payload, separators=(",", ":")) but signs json.dumps(payload) -- default separators, with spaces -- a second, independent serialization of the same dict that produces different bytes for any payload with more than one key.
Expected behavior
The bytes signed and the bytes sent must be the exact same bytes -- not two independent serializations of the same dict that happen to usually agree.
Steps to reproduce
pytest practice_tickets/tests/test_ticket_11_callback_signature_wrong_bytes.py -vjson.dumps(payload, separators=(",", ":")) (the bytes actually sent) against json.dumps(payload) (the bytes signed) for any payload with more than one key.Why this matters
This is a two-serializations-of-one-value bug: body and the argument to sign() both start from the same payload dict but take two different, independently-written paths to bytes. Nothing enforces that two calls to json.dumps on the same input produce the same output -- they don't, by design. The fix isn't "call json.dumps correctly" so much as "sign the bytes that already exist, don't reserialize." This also fails tests/test_callback.py::test_completion_delivers_signed_callback in the project's own suite.
Suggested approach
Look at how sign() is called: what it receives as its body argument right now, versus the body variable already computed two lines above it in the same function. The fix should mean sign() is only ever called with bytes that are also, unmodified, what gets passed to client.post(..., content=...).
Acceptance criteria
Verification
pytest practice_tickets/tests/test_ticket_11_callback_signature_wrong_bytes.py -v
Hints (0/2)
Try it without hints first — the reading is the exercise.
Working on this ticket
Work on a branch named for the ticket — that's what you'll submit.
Branch off your fork
Sign in to create it automatically, or do it yourself:
$git checkout -b fix/callback-signature-wrong-bytesFix it and commit
Meet every acceptance criterion, and add a test that would have caught this.
Push the branch
$git push -u origin fix/callback-signature-wrong-bytesSubmit it below
Paste your fork URL and the branch name, with a short write-up of the root cause.
Questions
Ask about anything unclear in the ticket — the maintainer and anyone who has solved it can answer. Please don't post full solutions.
Sign in to ask a question or reply.
Sign inhttp://localhost:8000localhost:6379curl localhost:8000/healthz{"status": "ok", "redis": true}Work the tickets in practice_tickets/tickets/ (ordered easy → hard); each names one dedicated pytest file in practice_tickets/tests/:
python3 -m venv .venv && source .venv/bin/activate
pip install -r requirements-dev.txt
pytest practice_tickets/tests/test_ticket_01_completed_jobs_counted_as_failed.py -v # a single ticket
./practice_tickets/run_tickets.sh # all 15, clean pass/fail summary
Each ticket test runs in its own pytest process with a 60s timeout, so one hung/crashed ticket can't take down the whole run or hide another ticket's result. Run pytest tests/ (the project's own suite, against fakeredis, hermetic and fast) after each fix to check for regressions -- the README documents exactly which fixes are expected to change that suite's results.
Level 1
Fix a bug
Read existing behaviour, correct it.