fix: shop-setup Phase 6 — webhook signature is sha1(body + secret), not HMAC-SHA1 - #27
Merged
JavadManashti merged 1 commit intoAug 17, 2026
Conversation
Phase 6 told the agent to verify with HMAC-SHA1 and to fulfil on `payment`
using `user.id`. Both are wrong for the flow this skill defaults to:
- Xsolla signs the raw body with plain `lowercase(sha1(rawBody + secret))`.
`webhooks-impl/references/signature-verification.md` states this explicitly
("not an HMAC key. This is plain sha1(body + key)").
- Phase 5 defaults to Method 1 (Store payment token), and Store orders deliver
`order_paid`, whose player field is `user.external_id` — `user.id` is absent
from that payload. Granting on `payment` also double-grants under separate
delivery.
Verified against `webhooks-impl/fixtures/order_paid.raw.txt`: the header
signature is bc951da3…, HMAC-SHA1 over the same bytes is 720c4486…, so a
handler built from Phase 6 as written rejected every genuine webhook with
400 INVALID_SIGNATURE — payment succeeds and the player receives nothing.
Brings the block in line with `webhooks-impl` (signature algorithm, event and
field mapping per token method, idempotency, and the 2xx / 400 INVALID_SIGNATURE
response contract) and mirrors it into `.cursor/skills/` as the provider sync
would. Five bullets in, five bullets out; no other section touched.
Reported externally in #26; landed here through internal review.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
There was a problem hiding this comment.
Pull request overview
This PR corrects Phase 6 (“Webhook / fulfillment”) in the shop-setup orchestrator skill so its webhook contract matches webhooks-impl and reflects Xsolla’s actual signature algorithm, event shapes, idempotency expectations, and response semantics.
Changes:
- Replaces the incorrect HMAC-SHA1 guidance with raw-body
sha1(body + secret)(secret appended) and constant-time comparison guidance. - Updates fulfillment-event guidance to distinguish
order_paid(Methods 1–2) vspayment(Method 3) and to avoid double-granting in separate delivery. - Updates acknowledgement semantics to accept
2xxon success and return400 INVALID_SIGNATUREon signature mismatch.
Reviewed changes
Copilot reviewed 2 out of 2 changed files in this pull request and generated 2 comments.
| File | Description |
|---|---|
| skills/shop-setup/SKILL.md | Updates Phase 6 webhook bullets to match webhooks-impl’s contract (signature, event selection, idempotency, response codes). |
| .cursor/skills/shop-setup/SKILL.md | Mirrors the same Phase 6 webhook contract changes in the Cursor-synced provider copy. |
💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.
Comment on lines
+203
to
+205
| - Verify the signature over the **raw** body: `lowercase(sha1(rawBody + secret))`, compared in constant time — plain SHA-1 with the secret **appended**, **not** HMAC-SHA1 → `webhooks-impl` | ||
| - Handle the fulfillment event for the token method in use: `order_paid` (Methods 1–2 — items in `items[]`, player in `user.external_id`) or `payment` (Method 3 — items usually in `custom_parameters`, player in `user.id`); when both arrive (separate delivery), grant on `order_paid` only | ||
| - Grant the item/currency/key in the partner's game system, idempotently by transaction id |
Comment on lines
+203
to
+205
| - Verify the signature over the **raw** body: `lowercase(sha1(rawBody + secret))`, compared in constant time — plain SHA-1 with the secret **appended**, **not** HMAC-SHA1 → `webhooks-impl` | ||
| - Handle the fulfillment event for the token method in use: `order_paid` (Methods 1–2 — items in `items[]`, player in `user.external_id`) or `payment` (Method 3 — items usually in `custom_parameters`, player in `user.id`); when both arrive (separate delivery), grant on `order_paid` only | ||
| - Grant the item/currency/key in the partner's game system, idempotently by transaction id |
y-klochikhin
approved these changes
Aug 17, 2026
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Corrects Phase 6 of
shop-setup, whose webhook contract contradictedwebhooks-impland produced a handler that rejects every genuine Xsolla webhook.shop-setupis the orchestrator loaded first for any "build me a shop" request, so Phase 6 is where an agent gets its webhook contract. Three of its five bullets were wrong.The defect
lowercase(sha1(rawBody + secret)).webhooks-impl/references/signature-verification.mdsays so outright — "not an HMAC key. This is plainsha1(body + key)".paymentis the wrong fulfillment event for our default flow. Phase 5 of this same skill defaults to Method 1 (Store payment token), and Store orders deliverorder_paid. Granting onpaymentalso double-grants under separate delivery.user.idis absent fromorder_paid. The player isuser.external_idthere;user.idis the Merchant-API (payment) shape.Verification
Against our own fixture
webhooks-impl/fixtures/order_paid.raw.txt:Every element of the corrected instruction resolves against the real payload; every element of the old one fails. The consequence on
maintoday is not cosmetic — the player pays and receives nothing, whichwebhooks-implcalls the number-one cause ofINVALID_SIGNATUREat go-live.The change
Five bullets in, five bullets out, now agreeing with
webhooks-impl:order_paid(items[],user.external_id) for Methods 1–2,payment(custom_parameters,user.id) for Method 3, granting onorder_paidonly when both arrive. This mapping is already stated inwebhooks-impl/references/testing.md.2xxon success,400+INVALID_SIGNATUREon mismatch — matchinghandler-and-reliability.md, which notes Xsolla's setup test explicitly checks for the 400No other section touched.
.cursor/skills/shop-setup/SKILL.mdis updated in the same commit and is byte-identical to the source (same git blob hash); runningsync-providers.ymllocally against this branch produces no further changes.Provenance
The defect was reported by an external contributor in #26. The finding was verified independently and landed here through internal review rather than merging that PR; #26 will be closed once this merges.