Webhooks API
Webhooks allow your backend to receive real-time HTTP POST notifications when events occur in the prediction engine. This is the recommended approach for backend-to-backend integration — more reliable than maintaining a persistent WebSocket connection from your server.
Authentication: X-Api-Key header required.
How It Works
Event Occurs → Prediction Engine → HTTP POST → Your Backend URL
(HMAC signed)
- You register a webhook URL with the prediction engine
- When matching events occur, the engine sends an HTTP POST to your URL
- The payload is signed with HMAC-SHA256 so you can verify authenticity
- Delivery is retried up to 3 times if your endpoint is unavailable
Register a Webhook
POST /api/v1/webhooks
Request Body:
| Field | Type | Required | Description |
|---|---|---|---|
url | string | Yes | The HTTPS URL to receive webhook deliveries |
events | string[] | No | Event types to subscribe to (empty = all events) |
description | string | No | Human-readable label |
Available Event Types:
| Event Type | Trigger |
|---|---|
MARKET_CREATED | A new market is created |
MARKET_STATUS_CHANGED | Market status changes (opened, suspended, closed, etc.) |
MARKET_RESOLVED | Market resolved with a winning outcome |
ORDER_BOOK_UPDATE | Order book changes after a trade or order placement |
PRICE_UPDATED | Market prices change |
ORDER_PLACED | A new order is placed |
ORDER_MATCHED | An order is matched and a trade executes |
ORDER_CANCELLED | An order is cancelled |
POSITION_UPDATED | A position changes |
SETTLEMENT_COMPLETED | A settlement is processed |
Example:
curl -X POST https://polymarket.sandbox.playbatman.com/api/v1/webhooks \
-H "X-Api-Key: your-api-key" \
-H "Content-Type: application/json" \
-d '{
"url": "https://your-backend.com/webhooks/prediction",
"events": ["ORDER_MATCHED", "MARKET_RESOLVED", "SETTLEMENT_COMPLETED"],
"description": "Production webhook"
}'
Response:
{
"success": true,
"data": {
"id": "webhook-uuid",
"operatorId": "operator-uuid",
"url": "https://your-backend.com/webhooks/prediction",
"secret": "a1b2c3d4e5f6...64-char-hex-secret",
"events": ["ORDER_MATCHED", "MARKET_RESOLVED", "SETTLEMENT_COMPLETED"],
"isActive": true,
"description": "Production webhook",
"createdAt": "2026-02-18T23:00:00.000Z",
"updatedAt": "2026-02-18T23:00:00.000Z"
}
}
Save the secret from the response — it is only shown in full when the webhook is created. You'll need it to verify webhook signatures.
List Webhooks
GET /api/v1/webhooks
Returns all webhooks for your operator. Secrets are truncated in the list response.
Response:
{
"success": true,
"data": [
{
"id": "webhook-uuid",
"operatorId": "operator-uuid",
"url": "https://your-backend.com/webhooks/prediction",
"secret": "a1b2c3d4...",
"events": ["ORDER_MATCHED", "MARKET_RESOLVED"],
"isActive": true,
"description": "Production webhook",
"createdAt": "2026-02-18T23:00:00.000Z",
"updatedAt": "2026-02-18T23:00:00.000Z"
}
]
}
Update Webhook
PATCH /api/v1/webhooks/{id}
Request Body (all fields optional):
| Field | Type | Description |
|---|---|---|
url | string | New delivery URL |
events | string[] | Updated event filter (empty = all) |
isActive | boolean | Enable or disable the webhook |
description | string | Updated description |
Example — disable a webhook:
curl -X PATCH https://polymarket.sandbox.playbatman.com/api/v1/webhooks/{webhookId} \
-H "X-Api-Key: your-api-key" \
-H "Content-Type: application/json" \
-d '{ "isActive": false }'
Delete Webhook
DELETE /api/v1/webhooks/{id}
Response:
{
"success": true,
"data": { "message": "Webhook deleted" }
}
Webhook Payload
Every webhook delivery is an HTTP POST with a JSON body:
{
"id": "delivery-uuid",
"sequence": 42,
"eventType": "ORDER_MATCHED",
"marketId": "market-uuid",
"operatorId": "operator-uuid",
"payload": {
"orderId": "order-uuid",
"marketId": "market-uuid",
"outcomeId": "outcome-uuid",
"operatorId": "operator-uuid",
"tradeId": "trade-uuid",
"side": "BUY",
"price": 0.65,
"shares": 50,
"filledShares": 50,
"remainingShares": 0,
"status": "FILLED",
"timestamp": 1708293600000
},
"timestamp": "2026-02-18T23:00:00.000Z"
}
Headers
| Header | Description |
|---|---|
Content-Type | application/json |
X-Webhook-Signature | sha256=<hex-encoded HMAC> |
X-Webhook-Event | The event type (e.g. ORDER_MATCHED) |
X-Webhook-Id | Unique delivery ID |
Verifying Signatures
Every webhook payload is signed with HMAC-SHA256 using your webhook secret. Always verify the signature before processing the payload.
Node.js
const crypto = require('crypto');
function verifyWebhook(body, signature, secret) {
const expected = crypto
.createHmac('sha256', secret)
.update(body)
.digest('hex');
return signature === `sha256=${expected}`;
}
// In your Express/Fastify handler:
app.post('/webhooks/prediction', (req, res) => {
const signature = req.headers['x-webhook-signature'];
const rawBody = JSON.stringify(req.body); // or use raw body middleware
if (!verifyWebhook(rawBody, signature, WEBHOOK_SECRET)) {
return res.status(401).send('Invalid signature');
}
const event = req.body;
console.log(`Received ${event.eventType}:`, event.payload);
res.status(200).send('OK');
});
Python
import hmac
import hashlib
def verify_webhook(body: bytes, signature: str, secret: str) -> bool:
expected = hmac.new(
secret.encode(), body, hashlib.sha256
).hexdigest()
return signature == f"sha256={expected}"
Delivery & Retries
- Timeout: 10 seconds per delivery attempt
- Retries: Up to 3 attempts — immediate, then after 5s, then after 30s
- Success: Any 2xx HTTP response is considered successful
- Fire-and-forget: Webhook failures never block order processing or other operations
Best Practices
- Respond quickly — Return a 200 status immediately and process the event asynchronously
- Verify signatures — Always validate the
X-Webhook-Signatureheader - Handle duplicates — Use the
sequencefield to deduplicate events - Use event filtering — Subscribe only to the events you need to reduce noise
- Monitor your endpoint — If your endpoint is consistently failing, deliveries will be lost after retries