# Webhook Batch Export

Instead of delivering events directly via webhook requests, Aghanim collects events over a time window, stores them as a JSONL file, and sends a `batch.ready` webhook notification with a pre-signed URL to download the batch.

## How it works

1.  You create a webhook and enable **Batch mode** on it.
2.  Aghanim collects events that match your webhook's subscriptions over a time window.
3.  Once the batch is ready, Aghanim sends a single `batch.ready` notification to your endpoint with a pre-signed URL to download the batch file.
4.  You download and process the JSONL file before the URL expires (24 hours).

## Enabling batch mode

### Via Dashboard

1.  Go to [**Game** → **Webhooks** → **Add webhook**](https://dashboard.aghanim.com/go/integration/webhooks/new) in the Aghanim Dashboard.
2.  Enter your endpoint URL and configure the webhook secret.
3.  Select the event types you want to receive in batches (e.g., `order.created`, `order.paid`).
4.  Turn on the **Batch mode** option.
5.  Save the webhook.

### Via API

Use the [Create Webhook](https://docs.aghanim.com/api-reference#/operations/create_webhook) API method with the `batch_mode` parameter set to `true`.

> **INFO:** Batch mode is **not available** for the following events:
> 
> *   `player.verify`, `player.lookup`, `store.get` — these events require a synchronous response from your server, which is incompatible with batched delivery.
> *   `item.add`, `item.remove` — these events should not be batched to avoid delays in item delivery.

## Handling batch notifications

When a batch is ready, Aghanim sends a `batch.ready` notification to your endpoint. Your endpoint must:

*   Accept HTTPS POST requests.
*   Verify the request [signature](https://docs.aghanim.com/webhooks/overview.md#secure-webhooks).
*   Download the batch file using the provided signed URL before it expires (24 hours).
*   Process the JSONL file — each line is a separate JSON event object.
*   Respond with 2xx status codes to signal successful processing, and 4xx or 5xx for errors.

### Notification schema

Below is an example of an `batch.ready` webhook request:

```http
POST /your/webhook/uri HTTP/1.1
Content-Type: application/json
Host: your-webhook-endpoint.com
User-Agent: Aghanim/0.1.0
X-Aghanim-Signature: 2e45ed4dede5e09506717490655d2f78e96d4261040ef48cc623a780bda38812
X-Aghanim-Signature-Timestamp: 1725548450

{
  "event_type": "batch.ready",
  "event_data": {
    "signed_url": "https://s2s-api.aghanim.com/s2s/v1/batch_export/download?token=eyJhb...NiIs",
    "format": "jsonl",
    "expires_at": 1710786400
  },
  "event_time": 1725548450,
  "event_id": "whevt_eCacGbJVbvToOgzjXUgOCitkQE",
  "idempotency_key": null,
  "request_id": "d1593e9c-c291-4004-8846-6679c2e5810b",
  "sandbox": false,
  "transaction_id": "whtx_eCacGbJVbvT",
  "context": null,
  "game_id": "gm_exTAyxPsVwh"
}
            
```

```shell
curl "https://your-webhook-endpoint.com/your/webhook/uri" \
    -X POST \
    -H "Content-Type: application/json" \
    -H "User-Agent: Aghanim/0.1.0" \
    -H "X-Aghanim-Signature: 2e45ed4dede5e09506717490655d2f78e96d4261040ef48cc623a780bda38812" \
    -H "X-Aghanim-Signature-Timestamp: 1725548450" \
    -d '{
      "event_type": "batch.ready",
      "event_data": {
        "signed_url": "https://s2s-api.aghanim.com/s2s/v1/batch_export/download?token=eyJhb...NiIs",
        "format": "jsonl",
        "expires_at": 1710786400
      },
      "event_time": 1725548450,
      "event_id": "whevt_eCacGbJVbvToOgzjXUgOCitkQE",
      "idempotency_key": null,
      "request_id": "d1593e9c-c291-4004-8846-6679c2e5810b",
      "sandbox": false,
      "transaction_id": "whtx_eCacGbJVbvT",
      "context": null,
      "game_id": "gm_exTAyxPsVwh"
    }'
            
```

### The Event schema

| Key | Type | Description |
| --- | --- | --- |
| `event_id` | `string` | Unique Event ID generated by Aghanim. |
| `game_id` | `string` | Your game ID in the Aghanim system. |
| `event_type` | `string` | The type of the event, `batch.ready` in this case. |
| `event_time` | `number` | The time the event was sent, in Unix epoch time. May differ across duplicate deliveries of the same event. |
| `event_data` | `EventData` | Contains the event-specific data, with possible keys for inherited objects. |
| `idempotency_key` | `string\|null` | Ensures webhook actions are executed only once, even if retried. Can be `null` depending on event type. |
| `request_id` | `string\|null` | If the event was triggered by an API request, the request ID is included. |
| `sandbox` | `boolean` | Indicates whether the event was sent from the sandbox game environment. |
| `trigger` | `string\|null` | The trigger that caused the event to be sent. |
| `transaction_id` | `string` | The transaction ID generated by Aghanim. This ID may be the same for multiple events emitted within the same transaction. |
| `context` | `EventContext\|null` | Contextual information about the event. |

### The EventContext schema

| Key | Type | Description |
| --- | --- | --- |
| `order` | `OrderContext\|null` | Order information associated with the event if applicable. |
| `player` | `PlayerContext\|null` | (Optional) Player information. To add this, enable "Add player context" in the webhook settings. |

### The EventData schema

| Key | Type | Description |
| --- | --- | --- |
| `signed_url` | `string` | Pre-signed URL to download the JSONL file. Expires after 24 hours. |
| `format` | `string` | File format. Always `jsonl`. |
| `expires_at` | `integer` | Unix timestamp when the signed URL expires. |

## Batch file format

Each batch file is a [JSONL](https://jsonlines.org/) file — one JSON event per line. Each line contains a complete webhook event object matching the event types you subscribed to.

**Example file content:**

```json
{"event_type":"order.created","event_data":{"id":"ord_eCacpFwavzi","amount":9499,"company_id":"com_exTAxmkZQCO","country":"US","created_at":1725547595,"currency":"USD","game_id":"gm_exTAyxPsVwh","items":[{"id":"itm_exTBZQmIlDz","name":"Crystals","sku":"crystals","quantity":480000,"price":9499,"price_decimal":94.99,"currency":"USD","type":"item","nested_items":null}],"modified_at":1725547657,"player_id":"2D2R-OP3C","receipt_number":"2409051289614565","status":"created","user_id":"usr_eymySUreClx","metadata":null,"creator":null},"event_time":1725547595,"event_id":"whevt_eCacFaIUauSnNfykXTfNChtsjDE","idempotency_key":"idmpt_aXRlb...JkX2VFS","request_id":"c0482e8b-b180-3003-7735-5568b2e4710a","sandbox":false,"trigger":"checkout.purchase","transaction_id":"whtx_eCacFaIUauS","context":null,"game_id":"gm_exTAyxPsVwh"}
{"event_type":"order.paid","event_data":{"id":"ord_eCacpFwavzi","amount":9499,"company_id":"com_exTAxmkZQCO","country":"US","created_at":1725547595,"currency":"USD","game_id":"gm_exTAyxPsVwh","revenue_usd":9099.123,"fees":{"aghanim_fee_usd":10.11,"payment_system_fee_usd":15.123,"taxes_usd":481.198933},"items":[{"id":"itm_exTBZQmIlDz","name":"Crystals","sku":"crystals","quantity":480000,"price":9499,"price_decimal":94.99,"currency":"USD","type":"item","nested_items":null}],"modified_at":1725547657,"player_id":"2D2R-OP3C","receipt_number":"2409051289614565","status":"paid","user_id":"usr_eymySUreClx","metadata":null,"creator":null},"event_time":1725548450,"event_id":"whevt_eCacGbJVbvToOgzjXUgOCitkQE","idempotency_key":"idmpt_aXRlb...JkX2VFS","request_id":"d1593e9c-c291-4004-8846-6679c2e5810b","sandbox":false,"trigger":"checkout.purchase","transaction_id":"whtx_eCacGbJVbvT","context":null,"game_id":"gm_exTAyxPsVwh"}
```

---
Source: https://docs.aghanim.com/webhooks/batching