Webhooks
Receive real-time event notifications at your own HTTPS endpoints
Socket events are for the signed-in browser on an open connection.
Webhooks are HTTPS posts to your server.
The socket stops when the tab closes. Failed webhook deliveries are retried on a bounded schedule.
Use the socket for live UI and webhooks for the record of the same action.
import { parseWebhookEvent } from "@agg-build/sdk/server";
const event = parseWebhookEvent(rawBody, req.headers, process.env.AGG_WEBHOOK_SECRET!);How it works
Register an endpoint
Create a webhook endpoint from the admin dashboard. Select a URL and the event types you
want to receive. The dashboard displays your signing secret (whsec_...) — store it in your
secrets manager immediately.
Receive events
sufwan-local-test sends POST requests to your URL with a JSON payload and Webhook-Signature header.
Verify the signature
Use parseWebhookEvent from the SDK to verify the signature and parse the event in one call.
This prevents spoofed requests.
Return 2xx
Verify and durably record the event, then respond promptly with a 2xx status. Process slow
work asynchronously. Timeouts and non-2xx responses trigger the retry schedule below.
Event types
| Event | Trigger | Payload |
|---|---|---|
accounts.created | New user created in your app | userId, appId, email |
accounts.linked | User linked to an external ID | userId, externalId |
wallets.ready | Server wallet provisioned | userId, evmAddress, svmAddress |
trades.placed | Trade order submitted | orderId, userId, side |
trades.filled | Trade order filled | orderId, userId, status |
trades.fee_reconciled | A self-custody trade's app fee reached its final state | orders, status, feeCollectedRaw, appTxHash |
markets.resolved | Market resolved (summary per app) | venueMarketId, outcomes[], affectedUsersCount |
deposits.confirmed | On-chain deposit landed in a user's managed wallet | userId, chainId, amountRaw, txHash |
markets.resolved carries outcomes[] (label, winner, externalIdentifier, payoutPerShare, refundAtCost). It does not carry a singular outcome. Shapes are on the event reference.
Webhook payloads are intentionally minimal — they carry IDs and timestamps. Query the API for
full details. accounts.created.email is populated when sufwan-local-test has an email from magic-link or
OAuth sign-in; otherwise it is null.
Delivery behavior
Deduplicate on the Webhook-Id header. It is the same value on every retry of a delivery.
Failed deliveries retry after 5 seconds, 5 minutes, 30 minutes, 2 hours, 5 hours, 10 hours, and another 10 hours. Each delay starts after the preceding failure. After retries are exhausted, inspect the delivery and replay it after fixing your endpoint.
Persistently failing endpoints can be disabled. Check endpoint status after an outage. Deliveries can be duplicated or arrive out of order, including for the same app. Use resource IDs to reconcile with current API state and deduplicate atomically.
Signature headers
| Header | Description |
|---|---|
Webhook-Id | Message id. Deduplicate on this header. It is the same value on every retry of a delivery. |
Webhook-Timestamp | Unix timestamp of the delivery attempt |
Webhook-Signature | HMAC-SHA256 signature for verification |
Rotate and replay
Create, delete, pause, and resume endpoints, and change their URL or event types, in the admin dashboard. These two calls use x-app-api-key only. Send an empty JSON body.
Rotate issues a new signing secret, returned once. You cannot read it back later.
import { AggAdminClient } from "@agg-build/sdk/server";
const admin = new AggAdminClient({
baseUrl: "https://api.t.sufwankhalid.com",
apiKey: process.env.AGG_APP_API_KEY!,
});
const { secret } = await admin.rotateWebhookSecret(appId, webhookId);Rotate webhook signing secret.
Replay re-queues the endpoint's failed deliveries from the last 7 days and returns { success }.
import { AggAdminClient } from "@agg-build/sdk/server";
const admin = new AggAdminClient({
baseUrl: "https://api.t.sufwankhalid.com",
apiKey: process.env.AGG_APP_API_KEY!,
});
const { success } = await admin.replayWebhookDeliveries(appId, webhookId);