---
title: From web store purchase to game wallet
nav: Purchase to wallet
order: 3.5
---
# From web store purchase to game wallet

**Credit the wallet from a verified server webhook, then refresh the balance in the game.** A checkout success screen or return URL is not proof of delivery. This walkthrough uses Appside's test integration; it does not activate live payments.

## The full flow
1. Your authenticated game backend issues a short-lived [player token](/docs/player-tokens/). Keep its private signing key on your server.
2. The game opens the store with that token. The store uses the published catalogue and the verified player identity.
3. A test purchase creates an `order.paid` event for your Fulfillment URL.
4. Your receiver verifies the signature over the raw request body and checks timestamp freshness before parsing or granting anything.
5. In one durable database transaction, record the `event_id`, add `order.entitlements.total` to the correct player's wallet, and write a ledger entry. Duplicate delivery must return success without another grant.
6. Return a 2xx only after committing. The game reads the updated balance from your authenticated backend.

Configure your public JWK, issuer, Fulfillment URL and webhook secret in Studio → Integration. See the [quickstart](/docs/quickstart/) for SDK setup and [webhooks](/docs/webhooks/) for the exact payload and headers. Use the current application base URL, `https://app.appside.build`, when configuring SDK clients.

## Verify the receiver before connecting checkout

Download the [standalone webhook proof](/docs/downloads/appside-webhook-proof.mjs), then run:

```sh
node appside-webhook-proof.mjs
```

This local proof exercises signature validation, a paid grant, replay protection and a refund. It is an in-memory reference, not a hosted payment test or a production wallet. A real receiver needs persistent storage, an atomic transaction and authorization on balance reads.

## Check the end-to-end integration

| Check | Expected result |
|---|---|
| Studio test probe | Acknowledge without changing balance |
| One test pack purchase | Increase by the signed event's `order.entitlements.total` |
| Replay that same event | No second grant |
| Invalid signature or stale timestamp | Reject without changing balance |
| Receiver temporarily unavailable | Delivery retries; eventual grant happens once |
| Refund and refund replay | Apply your refund policy once, never twice |
| Refund arrives before paid event | Record it so a delayed paid event cannot restore the refunded purchase |
| Refresh the game | Read the server wallet, not a number from the checkout URL |

Record the order ID, event ID and balance before and after each test. A payment acknowledgement and a wallet acknowledgement are different stages; keep both visible in your logs.

## Daily rewards use a different payload

For `reward.claimed`, the grant amount is the top-level `credits` field. Deduplicate on `event_id`, **not `reward_id`**: the reward name repeats across days. The seven-day calendar uses these daily claims. See the [daily reward payload](/docs/webhooks/).

## Production considerations

Persist the event and wallet update together. Reject unexpected tenants, currencies or event types according to your integration contract. Decide explicitly how refunds affect already-spent currency; do not silently overwrite your game's economy policy. A successful local proof establishes receiver behavior, not live payment readiness.
