Skip to main content

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)
  1. You register a webhook URL with the prediction engine
  2. When matching events occur, the engine sends an HTTP POST to your URL
  3. The payload is signed with HMAC-SHA256 so you can verify authenticity
  4. Delivery is retried up to 3 times if your endpoint is unavailable

Register a Webhook

POST /api/v1/webhooks

Request Body:

FieldTypeRequiredDescription
urlstringYesThe HTTPS URL to receive webhook deliveries
eventsstring[]NoEvent types to subscribe to (empty = all events)
descriptionstringNoHuman-readable label

Available Event Types:

Event TypeTrigger
MARKET_CREATEDA new market is created
MARKET_STATUS_CHANGEDMarket status changes (opened, suspended, closed, etc.)
MARKET_RESOLVEDMarket resolved with a winning outcome
ORDER_BOOK_UPDATEOrder book changes after a trade or order placement
PRICE_UPDATEDMarket prices change
ORDER_PLACEDA new order is placed
ORDER_MATCHEDAn order is matched and a trade executes
ORDER_CANCELLEDAn order is cancelled
POSITION_UPDATEDA position changes
SETTLEMENT_COMPLETEDA 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"
}
}
important

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):

FieldTypeDescription
urlstringNew delivery URL
eventsstring[]Updated event filter (empty = all)
isActivebooleanEnable or disable the webhook
descriptionstringUpdated 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

HeaderDescription
Content-Typeapplication/json
X-Webhook-Signaturesha256=<hex-encoded HMAC>
X-Webhook-EventThe event type (e.g. ORDER_MATCHED)
X-Webhook-IdUnique 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

  1. Respond quickly — Return a 200 status immediately and process the event asynchronously
  2. Verify signatures — Always validate the X-Webhook-Signature header
  3. Handle duplicates — Use the sequence field to deduplicate events
  4. Use event filtering — Subscribe only to the events you need to reduce noise
  5. Monitor your endpoint — If your endpoint is consistently failing, deliveries will be lost after retries