Webhooks

Portfolwright sends HTTPS POST requests to your endpoint for each event. Payloads are signed with HMAC-SHA256 using your webhook secret.

Rebalance events

Three events fire for each rebalance lifecycle.

rebalance.calculated
{
  "event": "rebalance.calculated",
  "id": "evt_4m2p9n",
  "created": 1750512600,
  "data": {
    "rebalance_id": "rb_7z4n9q",
    "portfolio_id": "pf_3x8k2m",
    "orders_pending": 3,
    "exchanges": ["XEUR", "XETR"],
    "estimated_tax_impact_eur": -312.40
  }
}
rebalance.completed
{
  "event": "rebalance.completed",
  "id": "evt_4m2p9r",
  "created": 1750512734,
  "data": {
    "rebalance_id": "rb_7z4n9q",
    "portfolio_id": "pf_3x8k2m",
    "orders_placed": 3,
    "orders_filled": 3,
    "tax_impact_eur": -312.40,
    "mifid_record_ids": ["mf_8k3p1r", "mf_8k3p1s", "mf_8k3p1t"]
  }
}

Signature verification

Every webhook delivery includes a Portfolwright-Signature header. Verify it before processing.

HMAC-SHA256 verification (Node.js)
const crypto = require("crypto");

function verifyWebhook(payload, sigHeader, secret) {
  const [ts, sig] = sigHeader.split(",");
  const hmac = crypto.createHmac("sha256", secret);
  hmac.update(ts + "." + payload);
  const expected = hmac.digest("hex");
  return crypto.timingSafeEqual(
    Buffer.from(sig), Buffer.from(expected)
  );
}

Retry behavior

If your endpoint returns any non-2xx status, Portfolwright retries with exponential backoff over 72 hours. Retry schedule: 5s → 30s → 2m → 10m → 1h → 6h → 24h → 72h. After 8 attempts, the delivery is marked failed and visible in your webhook event log.

Your endpoint must respond within 30 seconds. Return 200 as soon as possible — process the event asynchronously. Returning 200 idempotently for duplicate events (same event id) is safe and expected.

Back to docs home →