Webhooks (push events)
If you don't want to poll for state changes, register a webhook receiver. ProphetX pushes async events — contract updates, contract settlements, market-order state changes, and parlay state changes — to a URL you operate. You register one base URL per ISV, and ProphetX appends a fixed path to it for each event type.
Auth:
- On the management endpoints in this doc: ISV-only JWT (see Authentication).
- On inbound webhook deliveries to you: ProphetX-issued JWT signed by the push service. You should verify it on your end before trusting the payload.
1. Registration endpoints
| Method | Path | Purpose |
|---|---|---|
POST | /private/v1/push/register | { "baseUrl": "https://partner.example.com" } — register your receiver root. |
GET | /private/v1/push/register | Returns the current registration if one exists. |
DELETE | /private/v1/push/register | Stop receiving. Effective immediately. |
POST | /private/v1/push/test | { "type": "contract" | "contract_settlement" | "market_order" | "parlay" } — fires a test message and reports round-trip latency. |
POST /push/register returns:
201on success.409if you're already registered. To change the URL:DELETEfirst, then re-POST.
There is no per-event-type subscription. Once you're registered, you receive all event types.
POST /push/test response shape:
{ "success": true, "latencyMs": 142, "error": null }error is populated when the test couldn't reach you at all (connection refused, timeout).
2. What your receiver must implement
Your baseUrl must expose these paths:
| Method | Path | Why |
|---|---|---|
GET | /health | Called before resuming delivery after a pause. Return 200 when ready. |
POST | /push/contracts | ContractEvent — contract created / updated / deleted. |
POST | /push/contract-settlements | ContractSettlementEvent — contract settled. |
POST | /push/market-orders | MarketOrderEvent — market order created / updated / deleted. |
POST | /push/parlays | ParlayEvent — parlay created / updated / deleted. |
All POST events share one envelope.
3. The event envelope
{
"id": 8291,
"op": "created" | "updated" | "deleted" | "test",
"timestamp": "2026-04-01T12:00:00Z",
"data": { "...": "event-type-specific payload" }
}idis monotonically increasing. Use it to dedupe (you may receive duplicates on retry) and to order events (lateridwins).op = "test"indicates a test message sent viaPOST /push/test. Treat it as a no-op or a connectivity probe; don't apply state changes.
4. Event payloads
4.1 ContractEvent → POST /push/contracts
ContractEvent → POST /push/contracts{
"id": 8291,
"op": "updated",
"timestamp": "...",
"data": {
"eventId": 999999,
"marketId": 219,
"outcomeId": 4,
"contractId": "8315d4bc44d06eef736959e0b214c170",
"name": "Winner (incl. overtime)",
"type": "moneyline",
"price": -110.0,
"adjustedPrice": -115.0,
"strike": 0.0,
"status": "active",
"favourite": false,
"updatedAt": "2026-04-01T12:00:00.000Z"
}
}op semantics for contracts:
created— new contract, or new price available.updated— status orfavouritechanged.deleted— contract no longer available.
status is one of active, suspended, settled. price and adjustedPrice may be null if no liquidity exists yet.
4.2 ContractSettlementEvent → POST /push/contract-settlements
ContractSettlementEvent → POST /push/contract-settlements{
"id": 9001,
"op": "created",
"timestamp": "...",
"data": {
"contractId": "8315...",
"result": "profit",
"eventId": 999999,
"marketId": 219,
"outcomeId": 4,
"strike": 0.0
}
}result is one of profit, loss, push, void. This is the signal to mark the relevant orders' winningStatus in your own database.
4.3 MarketOrderEvent → POST /push/market-orders
MarketOrderEvent → POST /push/market-orders{
"id": 9100,
"op": "updated",
"timestamp": "...",
"data": {
"id": "isv_xyz",
"userId": "...",
"contractId": "8315...",
"quantity": 24.50,
"unfilledQuantity": 0,
"filledQuantity": 24.50,
"refundedQuantity": 0,
"expectedAveragePrice": -110,
"currentAveragePrice": -108,
"nextAveragePrice": -107,
"settlementStatus": "tbd",
"fillStatus": "filled",
"status": "completed",
"createdAt": "...",
"updatedAt": "..."
}
}Same shape as GET /private/v1/market-orders/{refId}, wrapped in the envelope. The data.id here is the order's refId.
4.4 ParlayEvent → POST /push/parlays
ParlayEvent → POST /push/parlaysFires when a parlay is created (i.e. after confirm) or updated (fill state changes, settlement, etc.).
{
"id": 9300,
"op": "updated",
"timestamp": "...",
"data": {
"id": "00000000-0000-0000-0000-000000000000",
"userId": "...",
"settlementStatus": "profit",
"settledAt": "...",
"createdAt": "...",
"updatedAt": "..."
}
}settlementStatus is one of profit, loss, push, tbd. For the full parlay state (fill status, fee, quantities, legs), call GET /private/v1/parlays/{parlayId} — the webhook is a lightweight signal that something changed, not a complete snapshot. See Parlays (2–12 leg trades).
5. A small naming inconsistency: fillStatus
fillStatus- Private API (
GET /market-orders/{refId}.fillStatus):filled|open|partial - Webhook (
MarketOrderEvent.data.fillStatus):filled|resting|partial
It's the same underlying state, just a different word for "not yet filled." Treat open and resting as synonyms.
6. Delivery, retries, and pausing
| Behavior | Default |
|---|---|
| Delivery timeout | 5 seconds — your receiver must respond 2xx within this window. |
| Retries | Non-2xx responses and timeouts are retried with a 10s delay. |
| Error threshold | After 3 consecutive failures, the receiver is paused and ProphetX stops sending. |
| Resume | ProphetX polls GET /health; when it returns 200, delivery resumes. |
| Max message age | 24 hours. Messages older than this are dropped, not delivered. |
7. Verifying inbound calls came from ProphetX
Each delivery carries a JWT signed by the push service (Ed25519). The push service's public key (or JWK endpoint) is provisioned by ProphetX out of band. Your handler should verify:
- Signature against the configured public key.
issmatches the expected push-service issuer.audis your registered baseUrl (or otherwise as configured).exp/nbfare valid against your clock.
Reject anything that doesn't pass — return non-2xx and ProphetX will retry. Don't act on unverified payloads.
8. Receiver implementation checklist
-
GET /healthreturns 200 when ready (and 5xx when you've intentionally paused). - All four POST paths are wired to handlers that parse the same envelope.
- Inbound JWT verified on every request.
- Events deduped on
id— a higheridfor the same entity wins. - Handlers are idempotent — the same event may arrive more than once.
- Responses go out in under 5 seconds; long-running work happens off the request path.
-
op: "test"is recognized and never applied to real state.
9. Curl
BASE="https://isv-staging-api.betprophet.co/private/v1"
# Register
curl -X POST "$BASE/push/register" \
-H "Authorization: Bearer $JWT_ISV" \
-H "Content-Type: application/json" \
-d '{ "baseUrl":"https://partner.example.com" }'
# Check
curl "$BASE/push/register" -H "Authorization: Bearer $JWT_ISV"
# Test each event type
for t in contract contract_settlement market_order parlay; do
curl -X POST "$BASE/push/test" \
-H "Authorization: Bearer $JWT_ISV" \
-H "Content-Type: application/json" \
-d "{\"type\":\"$t\"}"
done
# Unregister
curl -X DELETE "$BASE/push/register" -H "Authorization: Bearer $JWT_ISV"Updated about 7 hours ago
