Polyrankdocs

Webhooks

HMAC-signed event deliveries to your HTTPS endpoint — Stripe-style signatures, exponential retries.

Event catalog status

The webhook infrastructure (subscriptions, signing, retries, test deliveries) is live. The production event emitters for trader.fill, market.resolved, and signal.smart_money are coming soon — today you can create subscriptions and exercise your receiver end-to-end with polyrank.test events. Watch the changelog.

Manage subscriptions

Session-cookie auth; webhook quota is plan-gated (Free = 0 events → 403 plan_required, see Billing).

EndpointWhat it does
GET /v1/webhooksList subscriptions
POST /v1/webhooksCreate — https:// URLs only (SSRF-guarded)
PUT /v1/webhooks/{id}Update URL / event types / active flag
DELETE /v1/webhooks/{id}Remove
POST /v1/webhooks/{id}/testSend a synthetic polyrank.test event now

Creating a subscription returns the signing secret (whsec_…) once — store it like a password.

Verify signatures

Every delivery is signed Stripe-style:

X-Polyrank-Signature: t=1765391234,v1=hex(hmac_sha256(secret, "{t}.{raw_body}"))
X-Polyrank-Event-Id: 01JXYZ…
X-Polyrank-Event-Type: polyrank.test
User-Agent: Polyrank-Webhook/1.0
import { createHmac, timingSafeEqual } from 'node:crypto';

export function verify(header: string, rawBody: string, secret: string): boolean {
  const parts = Object.fromEntries(header.split(',').map((kv) => kv.split('=')));
  const expected = createHmac('sha256', secret)
    .update(`${parts.t}.${rawBody}`)
    .digest('hex');
  // Reject stale timestamps (replay defense)
  if (Math.abs(Date.now() / 1000 - Number(parts.t)) > 300) return false;
  return timingSafeEqual(Buffer.from(expected), Buffer.from(parts.v1));
}
import hashlib, hmac, time

def verify(header: str, raw_body: bytes, secret: str) -> bool:
    parts = dict(kv.split("=", 1) for kv in header.split(","))
    if abs(time.time() - int(parts["t"])) > 300:
        return False
    expected = hmac.new(
        secret.encode(), f"{parts['t']}.".encode() + raw_body, hashlib.sha256
    ).hexdigest()
    return hmac.compare_digest(expected, parts["v1"])

Always verify over the raw request body, before any JSON parsing.

Delivery & retries

  • 10-second POST timeout; any 2xx counts as delivered.
  • Failures retry with backoff: 5s → 30s → 5m → 1h → 6h → 24h (6 attempts max).
  • Deduplicate by X-Polyrank-Event-Id — retries reuse the id.
  • Deliveries count against your plan's monthly webhook-event quota.

Event types

TypeStatusPayload
polyrank.testliveSynthetic event from the /test endpoint
trader.fillcoming soonA followed wallet's fill
market.resolvedcoming soonMarket resolution
signal.smart_moneycoming soonSmart-money signal event

On this page