Webhooks impl
Implements and validates the backend that receives Xsolla webhooks for a headless web shop — server-to-server notifications for user_validation, payment / order_paid, refund / order_canceled, and subscription events. Covers SHA-1 signature verification, idempotency, retry/response handling, IP allowlisting, payload shapes per delivery mode, and the common mistakes that break go-live. Use when building, modifying, debugging, or reviewing a webhook handler — including "implement Xsolla webhooks", "verify webhook signature", "handle payment / order_paid / refund webhooks", "grant items after purchase", "webhook returns INVALID_SIGNATURE", "webhook idempotency / retries", or "headless webshop backend".From its SKILL.md
npx -y skills add xsolla/xsolla-ai-kit --skill webhooks-implAssembled from the repository path, not quoted from the project. Check it against their README if it does not work.
One thing to look at
- 2 stars2 stars. Stars are a popularity signal and not a quality one, but at this level it is likely that nobody has read this closely except its author, and you would be relying on your own review.
SKILL.md
7.8 KB, as published. Nobody here has run it
Status
This skill is a draft authored by the webhooks SME (@e.chernykh).
Detailed material lives in references/:
references/events-and-payloads.md— delivery modes, event types, payload JSON, refund codesreferences/signature-verification.md— SHA-1 algorithm + Node/PHP examplesreferences/handler-and-reliability.md— responses, idempotency, retries, IP allowlist, fulfillment (ask how goods are granted), full handlerreferences/testing.md— fixture replay vs live tunnel; whenpaymentvsorder_paidarrivesfixtures/— sampleuser_validation/order_paid/paymentbodies (+.raw.txt)
When to use
Use this skill when the developer wants to receive and process Xsolla webhooks on their own backend for a headless web shop (their storefront on the Xsolla catalog + Pay Station, without Site Builder). Trigger conditions:
- Implementing a webhook endpoint that grants items / currency after purchase
- Verifying the webhook signature or debugging
INVALID_SIGNATURE - Handling
user_validation,payment/order_paid,refund/order_canceled, or subscription events - Making webhook processing idempotent or handling retries correctly
- Reviewing an existing webhook handler before go-live
This skill covers handler implementation and how to test it. Enabling
webhooks + creating the secret in Publisher Account is a manual step in
Prerequisites below (not done by merchant-setup). Setting the live URL is
covered in references/testing.md.
Prerequisites
export XSOLLA_WEBHOOK_SECRET=<project webhook secret key> # server-side only, never client-side
export XSOLLA_ENV=sandbox # default; "production" for prod
Publisher Account — enable webhooks + secret (human)
The agent cannot create the secret in PA. Ask the developer to:
-
Open project webhooks (ids from
.envpermerchant-setup):https://publisher.xsolla.com/{XSOLLA_MERCHANT_ID}/projects/{XSOLLA_PROJECT_ID}/edit/webhooks -
Create / generate the webhook secret key (project secret used to sign notifications).
-
Enable webhooks for the project (Store / required notifications as shown in PA).
-
Put the secret in the backend
.env(never the browser / never commit real values):XSOLLA_WEBHOOK_SECRET=<the key from PA> -
Later set the listener URL (HTTPS) — local tunnel or deployed host — under the same webhooks settings (store path often
.../edit/webhooks/store). Seereferences/testing.md.
Without the secret in .env, signature verification always fails. Without
webhooks enabled + a reachable HTTPS URL, Xsolla never delivers live events
(fixtures still work for local replay).
Also needed:
- A backend that can read the raw request body (needed for signature verification) and a datastore for idempotency keys.
- Webhook URL must be HTTPS when Xsolla calls it — HTTP is rejected.
Steps
-
Know your delivery mode. Projects registered after 2025-01-22 get combined webhooks (
order_paid/order_canceledcarry items + payment); older projects get separate (payment+refundplusorder_paid/order_canceled). The JSON differs per mode — seereferences/events-and-payloads.md. -
Capture the raw body and verify the signature first. The
authorizationheader isSignature <40-hex>; recomputelowercase(sha1(rawBody + secret))and compare in constant time. Reject mismatches with400 INVALID_SIGNATURE. Full Node/PHP examples:references/signature-verification.md. -
Route by
notification_typeand fulfill:user_validation→ confirm the user exists;204if yes,400 INVALID_USERif no.order_paid→ grant purchased goods (idempotently).order_canceled→ revoke per the developer’s policy.- In separate mode,
payment/refundalso arrive — treat them as financial records only; do fulfillment onorder_*to avoid double-granting. - Merchant-only tokens may send
paymentwithout a Store order — then fulfill onpaymentif that is the only success signal (seetesting.md). - Subscription events (optional) → start / extend / revoke.
Ask the developer how users receive goods in their product (inventory API,
wallet, game server, …) and wire the handler to that path — do not invent a
parallel grant system. Details:
references/handler-and-reliability.md(Fulfillment).
-
Make fulfillment idempotent. Dedupe by transaction id (
billing.transaction.idcombined,transaction.idseparate) using a unique constraint; on a duplicate, return the prior2xxwithout re-granting. -
Respond correctly and keep it fast.
200/201/204= success,400(+ error code) = permanent reject,5xx= transient (Xsolla retries). Target < 3s; do heavy work async. Webhooks are delivered sequentially — a failing handler blocks the rest of the flow. -
Harden and test. Allowlist Xsolla's source IPs (still verify the signature). Prefer fixture replay for signature / routing / idempotency; use a public tunnel + sandbox purchase (developer sets the URL in PA) for live delivery. See
references/testing.md.
Common pitfalls
- Re-serializing the body before hashing — always sign the raw bytes. The #1
cause of
INVALID_SIGNATURE. - Returning
200on a bad signature — breaks Xsolla's setup test and hides attacks. Return400 INVALID_SIGNATURE. - Non-idempotent fulfillment — granting items twice on a retried/duplicate
webhook, or fulfilling on both
paymentandorder_paidin separate mode. Dedupe by transaction id and fulfill only onorder_*. - Misusing
5xx— it triggers up to 48h of retries for errors that will never succeed. Use400for permanent problems,5xxonly for transient ones. - Ignoring or slowing
user_validation— a failing/slow validation handler blocks every downstream payment webhook; the user sees an error. - Granting without asking how — inventing a demo inventory while the game already has a real entitlement path. Always ask how the buyer receives goods and map the webhook user id to that system.
Agent test
Prompt: "Implement an Xsolla webhook handler for my headless shop that grants items after purchase. Use Node.js/Express."
Result: the agent discovered and applied this skill (read SKILL.md + references,
including testing.md / fixtures), asked how goods are granted in the product,
scaffolded a raw-body Express endpoint, and smoke-tested with fixture POSTs:
lowercase(sha1(rawBody + secret)) rejecting tampered bodies with
400 INVALID_SIGNATURE; user_validation returning 204/400 INVALID_USER;
idempotent order_paid granting (replay returned 2xx with no re-grant); no
double-grant in separate mode (payment recorded only); 5xx reserved for
transient errors. ✅