Skip to main content

Payload Format

Every webhook delivery is a POST with a JSON body. Headers carry metadata for replay protection, signature verification, and retry tracking.

Body

Every event uses the same envelope:

{
"id": "evt_8c1f2a3b4d",
"type": "strategy.deployed",
"created_at": "2026-04-29T12:00:00.000Z",
"data": { ... }
}
FieldTypeDescription
idstringUnique event identifier, format evt_<10-12 hex>. Use this for idempotent deduplication.
typestringEvent type; one of the values enumerated below.
created_atstringISO 8601 UTC timestamp with millisecond precision indicating when the event was generated server-side.
dataobjectEvent-type-specific payload. Schemas are documented per type below.

Headers

HeaderDescription
X-PipAI-Event-IdUnique event ID. Identical to body.id. Use for idempotency.
X-PipAI-Event-TypeEvent type. Identical to body.type. Lets you route without parsing the body.
X-PipAI-Webhook-IdThe webhook subscription that this delivery belongs to (e.g. wh_2a4f10). Useful when one endpoint serves multiple subscriptions.
X-PipAI-Delivery-IdUnique per-delivery identifier. The same event_id redelivered as a retry will reuse this delivery ID across attempts.
X-PipAI-Delivery-AttemptInteger attempt counter starting at 1. Increments to 10 over the retry schedule before delivery is given up.
X-PipAI-TimestampUnix milliseconds at delivery time. Used for replay-window math during signature verification.
X-PipAI-SignatureHex-encoded HMAC-SHA256(webhook_secret, "{timestamp}.{raw_body}"). See Signature Verification.

Event types

Wildcard subscriptions (strategy.*, backtest.*, account.*, or *) are also accepted at registration time — see Create Webhook.

TypeDescriptionData schema
strategy.createdA strategy was created in draft status.strategy.created
strategy.deployedA strategy was deployed to production (or paper-trade if dry_run).strategy.deployed
strategy.pausedA strategy was paused; no new positions will open.strategy.paused
strategy.stoppedA strategy was stopped (terminal, not resumable).strategy.stopped
strategy.position_openedA new position was opened by the strategy.strategy.position_opened
strategy.position_closedAn open position was closed; PnL is final.strategy.position_closed
strategy.order_filledAn order placed by the strategy was filled (full or partial).strategy.order_filled
strategy.errorThe strategy hit an unrecoverable error and halted.strategy.error
backtest.job_doneA backtest job finished successfully.backtest.job_done
backtest.job_failedA backtest job aborted with an error.backtest.job_failed
account.balance_lowA monitored asset balance fell below the configured threshold.account.balance_low
account.api_key_rotatedAn API key on the account was rotated.account.api_key_rotated

Per-event data schemas

strategy.created

{
"strategy_id": "strat_8f2a1b",
"name": "BTC grid 1h",
"status": "draft"
}

strategy.deployed

{
"strategy_id": "strat_8f2a1b",
"name": "BTC grid 1h",
"capital": "10000.00",
"leverage": 3,
"deployed_at": "2026-04-29T12:00:00.000Z"
}

strategy.paused

{
"strategy_id": "strat_8f2a1b",
"status": "paused"
}

strategy.stopped

{
"strategy_id": "strat_8f2a1b",
"status": "stopped"
}

strategy.position_opened

{
"strategy_id": "strat_8f2a1b",
"position_id": "pos_19acb2",
"symbol": "BTCUSDT",
"side": "long",
"entry_price": "67234.50",
"qty": "0.05",
"leverage": 3,
"opened_at": "2026-04-29T12:00:01.245Z"
}

strategy.position_closed

{
"strategy_id": "strat_8f2a1b",
"position_id": "pos_19acb2",
"symbol": "BTCUSDT",
"side": "long",
"entry_price": "67234.50",
"exit_price": "68110.00",
"qty": "0.05",
"exit_qty": "0.05",
"pnl": "43.78",
"leverage": 3,
"opened_at": "2026-04-29T12:00:01.245Z",
"closed_at": "2026-04-29T13:42:18.880Z"
}

strategy.order_filled

{
"strategy_id": "strat_8f2a1b",
"order_id": "ord_3c9df4",
"symbol": "BTCUSDT",
"side": "buy",
"type": "limit",
"price": "67234.50",
"qty": "0.05",
"filled_at": "2026-04-29T12:00:01.180Z"
}

strategy.error

{
"strategy_id": "strat_8f2a1b",
"error_code": "ORDER_REJECTED",
"message": "Exchange rejected order: insufficient margin"
}

backtest.job_done

{
"job_id": "bt_4d2e7c1f",
"metrics": {
"total_return": "0.184",
"sharpe": "1.62",
"max_drawdown": "-0.073",
"win_rate": "0.541",
"total_trades": 312,
"profit_factor": "1.78"
}
}

See the Backtesting metrics reference for the full metric definitions.

backtest.job_failed

{
"job_id": "bt_4d2e7c1f",
"error_code": "TIMEOUT",
"message": "Job exceeded maximum runtime of 30 minutes"
}

account.balance_low

{
"asset": "USDT",
"balance": "12.50",
"threshold": "100.00"
}

The threshold is configured per webhook via balance_low_threshold at creation time — see Create Webhook.

account.api_key_rotated

{
"key_id": "key_7a91c2",
"rotated_at": "2026-04-29T12:00:00.000Z"
}

Acknowledgement

Your endpoint must respond with a 2xx HTTP status within 10 seconds of receiving the request. Any other outcome — non-2xx response, slow response, TLS error, or connection failure — is treated as a delivery failure and queued for retry per the schedule in the Overview.

The response body is ignored. An empty 200 OK is fine.

If you need to perform expensive work in response to an event, acknowledge synchronously and dispatch the work to a background queue. Holding the connection open while you process events will starve the delivery worker and trigger unnecessary retries.