> ## Documentation Index
> Fetch the complete documentation index at: https://docs.winampay.de/llms.txt
> Use this file to discover all available pages before exploring further.

# Webhooks

> Receive and verify payment outcome notifications from winam-payments.

## Overview

When a transaction reaches a terminal state (`succeeded` or `failed`), winam-payments sends a `POST` JSON request to your `callback_url`.

A late deposit confirmation (operator SMS/USSD arriving after the transaction expired or failed) does **not** auto-credit: the transaction first moves to `requires_manual_resolution` and waits for a Winam operator decision. The webhook is sent only once that decision resolves it to `succeeded` (`payment.succeeded`) or `failed` (`payment.failed`). See [State Machine](/api-reference/state-machine).

<Note>
  Webhooks are the recommended way to receive payment outcomes — faster and more reliable than polling. Always provide a `callback_url` in your deposit and withdrawal requests.
</Note>

## Webhook payload

```json theme={null}
{
  "winam_tx_id":    "7c9e6679-7425-40de-944b-e07fc1f90ae7",
  "reference":      "platform-bet-slip-98765",
  "event":          "payment.succeeded",
  "direction":      "deposit",
  "amount_xaf":     5000,
  "operator":       "mtn",
  "provider_tx_id": "MTN2025010112345",
  "ts":             "2025-01-01T12:07:43.123456+00:00"
}
```

| Field            | Possible values                                 |
| ---------------- | ----------------------------------------------- |
| `event`          | `payment.succeeded` or `payment.failed`         |
| `direction`      | `deposit` or `withdrawal`                       |
| `provider_tx_id` | Provider-side reference (null if not available) |
| `ts`             | ISO 8601 UTC timestamp of the event             |

## Signature verification

Each webhook includes an `X-Winam-Signature` header containing a **JWT RS256** token signed with Winam's private key. **Always verify this signature** before processing the webhook.

<Steps>
  <Step title="Fetch the public key (once)">
    ```bash theme={null}
    curl https://api.winampay.de/api/v1/security/callback-public-key
    ```

    Cache this response — the key rotates rarely.
  </Step>

  <Step title="Verify the signature">
    <CodeGroup>
      ```python Python theme={null}
      import jwt
      import httpx

      # 1. Fetch the public key (cache this)
      r = httpx.get("https://api.winampay.de/api/v1/security/callback-public-key")
      public_key = r.json()["public_key_pem"]

      # 2. Verify the received webhook
      def verify_webhook(request_headers: dict, request_body: dict) -> bool:
          signature = request_headers.get("X-Winam-Signature")
          if not signature:
              return False
          try:
              decoded = jwt.decode(
                  signature,
                  public_key,
                  algorithms=["RS256"],
              )
              return decoded["winam_tx_id"] == request_body["winam_tx_id"]
          except jwt.InvalidTokenError:
              return False
      ```

      ```javascript Node.js theme={null}
      const jwt = require('jsonwebtoken');

      function verifyWebhook(headers, body) {
        const signature = headers['x-winam-signature'];
        if (!signature) return false;
        try {
          const decoded = jwt.verify(signature, PUBLIC_KEY_PEM, { algorithms: ['RS256'] });
          return decoded.winam_tx_id === body.winam_tx_id;
        } catch (e) {
          return false;
        }
      }
      ```
    </CodeGroup>
  </Step>
</Steps>

## Retry policy

winam-payments retries **3 times** with exponential backoff (1 s → 2 s → 4 s) if your endpoint responds with a status ≥ 400 or does not respond within 5 seconds.

**Your endpoint must:**

* Respond `2XX` within **5 seconds**
* Be **idempotent** — the same `winam_tx_id` may be delivered more than once
* Return `200` even if the event was already processed

<Warning>
  If all retries fail, no further delivery attempts are made. Use [Replay Webhook](/api-reference/replay-webhook) for manual recovery.
</Warning>

For the public key endpoint reference, see [Callback Public Key](/api-reference/callback-public-key).
