---
title: "Webhook Event Reference"
description: "Full payload schemas for every webhook event type"
---

> **For AI agents:** the complete documentation index is at [llms.txt](/llms.txt). Append `.md` to any page URL for its markdown version.

All webhook events share the same envelope format:

```json
{
  "id": "evt_accounts.created:usr_abc123",
  "type": "accounts.created",
  "createdAt": "2026-04-09T18:15:03.000Z",
  "data": { ... }
}
```

| Field | Type | Description |
|-------|------|-------------|
| `id` | string | Unique event ID (use for deduplication) |
| `type` | string | Event type (dot notation) |
| `createdAt` | string | ISO 8601 timestamp |
| `data` | object | Event-specific payload |

---

## accounts.created

Fired when a new user is created in your app (first authentication).

```json
{
  "id": "evt_accounts.created:usr_abc123",
  "type": "accounts.created",
  "createdAt": "2026-04-09T18:15:03.000Z",
  "data": {
    "userId": "usr_abc123",
    "appId": "app_xyz",
    "createdAt": "2026-04-09T18:15:03.000Z",
    "email": "user@example.com"
  }
}
```

| Field | Type | Description |
|-------|------|-------------|
| `userId` | string | The new user's ID |
| `appId` | string | Your app's ID |
| `createdAt` | string | When the user was created |
| `email` | string \| null | The user's email address when AGG has one from magic link or OAuth, otherwise `null` |

---

## accounts.linked

Fired when a user is linked to a partner external ID via the identity assertion flow.

```json
{
  "id": "evt_accounts.linked:usr_abc123:partner_42",
  "type": "accounts.linked",
  "createdAt": "2026-04-09T18:15:03.000Z",
  "data": {
    "userId": "usr_abc123",
    "externalId": "partner_42",
    "linkedAt": "2026-04-09T18:15:03.000Z"
  }
}
```

| Field | Type | Description |
|-------|------|-------------|
| `userId` | string | The user's AGG ID |
| `externalId` | string | Your internal user ID |
| `linkedAt` | string | When the link was created |

---

## wallets.ready

Fired when a server-managed wallet is provisioned for a user (both EVM and Solana addresses
are available).

```json
{
  "id": "evt_wallets.ready:usr_abc123",
  "type": "wallets.ready",
  "createdAt": "2026-04-09T18:15:03.000Z",
  "data": {
    "userId": "usr_abc123",
    "evmAddress": "0x1234...abcd",
    "svmAddress": "ABC123...xyz",
    "createdAt": "2026-04-09T18:15:03.000Z"
  }
}
```

| Field | Type | Description |
|-------|------|-------------|
| `userId` | string | The user's ID |
| `evmAddress` | string | EVM (Ethereum/Polygon) wallet address |
| `svmAddress` | string | Solana wallet address |
| `createdAt` | string | When the wallet was provisioned |

---

## trades.placed

Fired when a user submits a trade order. The payload contains the order ID — query the API for
the full breakdown (venue splits, amounts, prices).

```json
{
  "id": "evt_trades.placed:quote_abc123",
  "type": "trades.placed",
  "createdAt": "2026-04-09T18:15:03.000Z",
  "data": {
    "orderId": "quote_abc123",
    "userId": "usr_abc123",
    "side": "yes",
    "timestamp": "2026-04-09T18:15:03.000Z"
  }
}
```

| Field | Type | Description |
|-------|------|-------------|
| `orderId` | string | The order/quote ID (query API for details) |
| `userId` | string | The user who placed the trade |
| `side` | string | Outcome side: `"yes"` or `"no"` |
| `timestamp` | string | When the order was placed |

---

## trades.filled

Fired when a trade order reaches a terminal fill state. One event per venue-level order fill.

```json
{
  "id": "evt_trades.filled:ord_abc123",
  "type": "trades.filled",
  "createdAt": "2026-04-09T18:15:03.000Z",
  "data": {
    "orderId": "ord_abc123",
    "userId": "usr_abc123",
    "status": "filled",
    "timestamp": "2026-04-09T18:15:03.000Z"
  }
}
```

| Field | Type | Description |
|-------|------|-------------|
| `orderId` | string | The individual order ID (query API for fill details) |
| `userId` | string | The user who placed the trade |
| `status` | string | `"filled"` or `"partial_fill"` |
| `timestamp` | string | When the fill was recorded |

---

## markets.resolved

Fired once per app when a prediction market resolves. Contains a summary — use the pull endpoint
for the full list of affected users.

```json
{
  "id": "evt_markets.resolved:vm_123:app_xyz",
  "type": "markets.resolved",
  "createdAt": "2026-04-09T18:15:03.000Z",
  "data": {
    "venueMarketId": "vm_123",
    "externalIdentifier": "KXBTC-26APR09",
    "status": "resolved",
    "outcomes": [
      { "label": "Yes", "winner": true },
      { "label": "No", "winner": false }
    ],
    "affectedUsersCount": 1842,
    "detailsPath": "/apps/app_xyz/market-resolutions/vm_123/users"
  }
}
```

| Field | Type | Description |
|-------|------|-------------|
| `venueMarketId` | string | The resolved market ID |
| `externalIdentifier` | string | Venue-specific market identifier |
| `status` | string | `"resolved"` or `"closed"` |
| `outcomes` | array | Resolution outcome for each option |
| `affectedUsersCount` | number | Users in your app with open positions |
| `detailsPath` | string | API path to paginate affected user IDs |

### Querying affected users

Use the SDK to paginate through affected users. Pass `includeEmails: true` to get email addresses
where available.

```typescript
import { AggAdminClient } from "@agg-build/sdk/server";

const admin = new AggAdminClient({
  baseUrl: "https://api.agg.market",
  apiKey: process.env.AGG_API_KEY!,
});

// Paginate through all affected users
let cursor: string | undefined;
do {
  const page = await admin.listMarketResolutionUsers(
    appId,
    event.data.venueMarketId,
    { limit: 100, cursor, includeEmails: true },
  );
  for (const user of page.data) {
    console.log(user.userId, user.email);
  }
  cursor = page.nextCursor ?? undefined;
} while (cursor);
```

See the [List users affected by market resolution](/api-reference/app-management/list-users-affected-by-market-resolution)
API reference for full details.

<Info>
  This endpoint returns the current set of users with non-zero positions, not an immutable
  snapshot at resolution time. If positions are modified after resolution, the results may differ
  from the `affectedUsersCount` in the webhook.
</Info>
