Skip to main content
← Back to Guides
Advanced📖 18 min

API and Webhooks Integration: Wiring Predite Into Your Own Stack

Most of Predite lives in the dashboard, but the most interesting edge cases happen when you stop clicking and start scripting. Maybe you want EV signals piped into a Discord channel the moment the scanner finds them. Maybe you run a bot off-platform and need your live Polymarket positions as JSON every minute. Maybe you want a stop-loss trigger to page you, post to Slack, and kick off an n8n workflow that closes the position automatically. That is what the Predite API and webhook system exist for.

This guide walks through the whole integration surface end to end: generating an API key, calling the REST endpoints with Bearer auth, respecting rate limits, subscribing to webhook events, verifying delivery signatures with HMAC-SHA256, and finally stitching it all into a custom alerting pipeline. Everything here is Bot plan only ($99/mo) — the Starter ($29) and Pro ($59) plans use the dashboard and built-in notifications, but programmatic access (API keys and outbound webhooks) is gated to Bot.

Generating an API Key

Predite uses long-lived API keys for the REST API. They are scoped to your account, so any key can read your portfolio and the signal feed, and nothing else — there are no write endpoints, so a leaked key cannot place trades or move funds.

  1. Open Settings → API in the dashboard. If you are not on the Bot plan you will see an upgrade prompt instead of the key manager.
  2. Click Create API key and give it a descriptive label, like `n8n-prod` or `discord-bot`. The label is just for your own bookkeeping when you have several keys.
  3. Predite generates the key and shows it to you exactly once. Copy it immediately and store it in a secrets manager or your environment.

A key looks like this:

``` pdt_live_a1b2c3d4e5f6a7b8c9d0e1f2a3b4c5d6 ```

The format is the prefix `pdt_live_` followed by 32 hex characters — 40 characters total. On the server, only a SHA-256 hash of the full key is stored, plus the first 16 characters (`pdt_live_` + 7 chars) as a non-secret prefix so the dashboard can show you `pdt_live_a1b2c3…` in the key list without ever storing the secret in clear text. That is also why we cannot show you the full key again after creation: we literally do not have it.

A few operational notes:

  • Revoking a key is a soft delete. The row is kept for audit, the status flips to `revoked`, and any request using it immediately starts returning 401. Rotate by creating a new key, deploying it, then revoking the old one.
  • The dashboard tracks `last_used_at` and a request counter per key so you can spot a key that has gone quiet (a sign your integration broke) or one that is busier than expected (a sign it leaked).
  • Treat the key like a password. Never commit it, never put it in client-side JavaScript, never paste it into a screenshot. All examples below read it from an environment variable for that reason.

Authenticating Requests

Every call to the REST API carries the key in the standard `Authorization` header as a Bearer token:

``` Authorization: Bearer pdt_live_a1b2c3d4e5f6a7b8c9d0e1f2a3b4c5d6 ```

The server extracts the token, hashes it, looks up the matching active key, and compares hashes. If the header is missing, malformed, points at a revoked key, or the key has expired, you get:

```json { "error": "invalid_api_key" } ```

with HTTP status 401. There is no separate login step and no token refresh — the key is the credential. Set it once in your client and you are done.

The REST Endpoints

The v1 API is deliberately small and read-only. Two endpoints cover the data most integrations actually need: your portfolio and the signal feed. Both live under `/api/v1/` and both return JSON with a `generated_at` ISO timestamp so you can tell how fresh a response is.

GET /api/v1/portfolio

Returns your live positions and a portfolio summary, read from the Polymarket wallet you connected in Settings → Platforms.

```bash curl -s https://app.predite.io/api/v1/portfolio \ -H "Authorization: Bearer $PREDITE_API_KEY" ```

A typical response:

```json { "wallet": "0x3a1f...8c2d", "positions": [ { "market": "Will the Fed cut rates in July 2026?", "outcome": "Yes", "shares": 420.0, "avg_price": 0.61, "current_price": 0.68, "value": 285.6, "unrealized_pnl": 29.4 } ], "summary": { "total_value": 4128.55, "unrealized_pnl": 312.10, "open_positions": 7 }, "generated_at": "2026-06-01T14:32:08.114Z" } ```

If you have not connected a wallet yet, you will get `{ "positions": [], "summary": null, "error": "no_wallet_connected" }` with a 200 status — so check for the `error` field rather than assuming a 200 means data. The endpoint reads the same on-chain position data the dashboard uses, so P&L here matches what you see in the UI.

GET /api/v1/signals

Returns the top EV (expected value) signals from the market scanner — the same engine that powers the dashboard's signal feed. Each signal compares the current market price against Predite's AI-estimated fair price and reports the edge in percentage points.

It accepts two query parameters:

  • `limit` — how many signals to return, 1 to 100, default 20.
  • `min_edge` — minimum edge in percentage points, default 5. Use this to filter out marginal signals.

```bash curl -s "https://app.predite.io/api/v1/signals?limit=10&min_edge=8" \ -H "Authorization: Bearer $PREDITE_API_KEY" ```

```json { "signals": [ { "market_title": "Will Bitcoin close above $120k on 2026-06-30?", "direction": "YES", "edge_pp": 11.4, "market_price": 0.42, "ai_price": 0.534, "created_at": "2026-06-01T14:05:00.000Z" } ], "count": 1, "filters": { "min_edge": 8, "limit": 10 }, "generated_at": "2026-06-01T14:32:40.781Z" } ```

Here `edge_pp` of 11.4 means the AI fair price (0.534) sits 11.4 points above the market price (0.42) on the YES side — the kind of gap an EV trader looks for. Results are sorted by edge descending, then by recency. The `filters` block echoes back what was applied so you can confirm your parameters parsed correctly.

A Python client

Here is a small wrapper that handles auth and both endpoints. It uses `requests`, but the shape translates to any HTTP client.

```python import os import requests

BASE = "https://app.predite.io/api/v1" KEY = os.environ["PREDITE_API_KEY"] SESSION = requests.Session() SESSION.headers["Authorization"] = f"Bearer {KEY}"

def get_portfolio(): r = SESSION.get(f"{BASE}/portfolio", timeout=10) r.raise_for_status() return r.json()

def get_signals(limit=20, min_edge=5.0): params = {"limit": limit, "min_edge": min_edge} r = SESSION.get(f"{BASE}/signals", params=params, timeout=10) r.raise_for_status() return r.json()

if __name__ == "__main__": for s in get_signals(limit=5, min_edge=10)["signals"]: print(f"{s['edge_pp']:+.1f}pp {s['direction']:>3} {s['market_title']}") ```

Reusing one `Session` keeps the connection alive across calls, which matters once you are polling on a schedule.

Rate Limits

The API allows 1000 requests per hour per API key. That is generous for the read-only workload it is designed for — at one portfolio poll per minute you use 60 requests an hour, leaving plenty of headroom for signal checks on top.

A few habits keep you well inside the limit:

  • Do not poll faster than you need. EV signals refresh on the scanner's cron cadence, not continuously, so polling `/signals` every few seconds just burns quota returning identical data. Once a minute is plenty; once every few minutes is often enough.
  • Prefer webhooks for events. If you are polling to detect *that something happened* — a whale moved, a stop-loss fired — you are using the wrong tool. Webhooks (below) push those to you the instant they occur, with zero polling cost.
  • Cache within a tick. If three parts of your pipeline need the portfolio, fetch it once and pass it around rather than calling three times.
  • Back off on errors. If you ever get throttled, treat it as a signal to slow down, not to retry in a tight loop.

The mental model: use the REST API to pull state on demand, and use webhooks to react to events. Mixing those up is the most common cause of wasted quota.

Webhook Subscriptions

Webhooks flip the direction. Instead of you asking Predite "anything new?", Predite calls *your* URL the moment something happens. Because the payload is plain JSON over HTTPS POST, anything that can receive an HTTP request works as a destination: Discord and Slack incoming webhooks, automation platforms like n8n and Zapier, or your own server.

Creating a subscription

  1. Go to Settings → Webhooks (Bot plan required).
  2. Click Add webhook and paste your destination URL. In production this must be `https://` — plain HTTP is rejected.
  3. Give it a label and select which event types it should receive. A subscription must listen to at least one event.
  4. Save. Predite generates a signing secret and shows it in the subscription's detail view.

The secret looks like:

``` whsec_4f8c...d2a1 ```

(the prefix `whsec_` plus 64 hex characters). You will need it to verify deliveries — store it alongside the destination, ideally as an environment variable on the receiving service.

Event types

There are six event types you can subscribe to:

  • `whale_move` — a tracked whale wallet opened, increased, or closed a significant position.
  • `ev_signal` — the scanner found a new EV opportunity above the threshold.
  • `arb_opportunity` — a cross-platform arbitrage gap appeared between Polymarket and Kalshi on equivalent markets.
  • `stop_loss_triggered` — one of your configured stop-losses crossed its threshold.
  • `resolution_imminent` — a market you hold (or watch) is approaching resolution.
  • `bot_trade_executed` — one of your bots placed or closed a trade.

Subscribe each destination to only the events it cares about. Your on-call pager probably wants just `stop_loss_triggered`; a research channel might want `whale_move` and `ev_signal`; an automation flow that rebalances might want `bot_trade_executed`.

Delivery payload and headers

Every delivery is an HTTP `POST` with a JSON body shaped like:

```json { "event": "ev_signal", "timestamp": "2026-06-01T14:33:12.004Z", "data": { "market_title": "Will the ECB hold rates in June 2026?", "direction": "NO", "edge_pp": 9.2, "market_price": 0.71, "ai_price": 0.618 } } ```

The `event` field tells you the type, `timestamp` is when Predite dispatched it, and `data` is the event-specific payload. Alongside the body, Predite sends these headers:

  • `X-Predite-Event` — the event type, so you can route without parsing the body.
  • `X-Predite-Timestamp` — the dispatch time (matches the body's `timestamp`).
  • `X-Predite-Signature` — the HMAC-SHA256 signature, formatted `sha256=<hex>`.
  • `User-Agent: Predite-Webhooks/1.0`.

Your endpoint should respond with any `2xx` status quickly. The delivery times out after 5 seconds, so acknowledge fast and do heavy work asynchronously — return `200`, then process.

Reliability behavior

Delivery is not fire-and-forget on Predite's side. Failed deliveries (a non-2xx response, a timeout, or a network error) are logged, and transient `5xx`/network failures are retried with exponential backoff. If a subscription racks up 10 consecutive failures, Predite auto-disables it (status `disabled_by_failures`) to stop hammering a dead endpoint. You will see the failure reason in Settings → Webhooks, and re-enabling the subscription resets the failure counter. Each subscription also tracks last success, last failure, and total deliveries so you can audit health at a glance.

Verifying Webhook Signatures

Because your webhook URL is just an HTTP endpoint, anyone who learns it could POST fake events. The signature header is how you prove a delivery genuinely came from Predite and was not tampered with in transit.

The scheme is straightforward: Predite computes `HMAC-SHA256(raw_request_body, your_secret)` and sends the hex digest in `X-Predite-Signature` as `sha256=<digest>`. On your side you recompute the same HMAC over the raw, unparsed body and compare. The "raw body" part matters — if your framework parses JSON and you re-serialize it, whitespace and key ordering can differ and the signature will not match. Capture the bytes before parsing.

A timing-safe comparison in Python:

```python import hashlib import hmac

def verify(raw_body: bytes, signature_header: str, secret: str) -> bool: if not signature_header or not signature_header.startswith("sha256="): return False sent = signature_header.split("=", 1)[1] expected = hmac.new( secret.encode(), raw_body, hashlib.sha256 ).hexdigest() return hmac.compare_digest(sent, expected) ```

A minimal Flask receiver that rejects anything unsigned:

```python import os from flask import Flask, request, abort

app = Flask(__name__) SECRET = os.environ["PREDITE_WEBHOOK_SECRET"]

@app.post("/predite-webhook") def receiver(): raw = request.get_data() # raw bytes, before JSON parsing if not verify(raw, request.headers.get("X-Predite-Signature", ""), SECRET): abort(401) payload = request.get_json() event = payload["event"] # route on event, then return fast handle(event, payload["data"]) return "", 200 ```

Always use a constant-time comparison (`hmac.compare_digest`, not `==`) so you do not leak signature bytes through timing. And reject the request before doing any real work if verification fails — that one check is the difference between a trusted pipeline and an open door.

Building a Custom Alerting Pipeline

Let us tie it together with a concrete example: you want every high-conviction EV signal and every stop-loss trigger to land in Slack, and you want stop-losses to additionally fire an n8n workflow that records the event and (optionally) closes the position via your own broker integration.

  1. Create two webhook subscriptions in Settings → Webhooks. One points at your Slack incoming webhook URL and subscribes to `ev_signal`. The other points at your n8n webhook node and subscribes to `stop_loss_triggered`. Each gets its own secret.
  2. For Slack, the cleanest approach is a tiny relay (a serverless function works well): it verifies the Predite signature, formats the `data` into a Slack message, and posts to Slack's URL. The relay lets you filter — for example, only forward signals where `data.edge_pp >= 10` — so you are not noisy in the channel. Markets with a small edge get dropped; only the strong ones page the team.
  3. For n8n, put a Webhook trigger node first, then a Function node that runs the same HMAC check against the raw body before anything downstream executes. After verification, branch on `data` to log the event and call your broker. Zapier follows the same pattern with a Code step doing the verification.
  4. Add the REST API for context. When a `stop_loss_triggered` event arrives, your n8n flow can immediately call `GET /api/v1/portfolio` with your API key to pull the full current position set, so the alert is enriched with live P&L rather than just the trigger data. This is the read-on-demand pattern working alongside the push pattern.
  5. Make it idempotent. Networks retry; the same delivery can arrive twice. Key your downstream actions on something stable (the event plus its timestamp, or an ID inside `data`) so a duplicate delivery does not double-act.
  6. Monitor the monitor. Check Settings → Webhooks periodically for `disabled_by_failures`, and have your relay log every rejected signature — a sudden spike means either your secret rotated or someone is probing your endpoint.

With that in place you have a closed loop: Predite's scanner and risk engine push events the instant they fire, your relays verify and filter them, and your REST calls fill in live state on demand — all without a single polling loop burning quota.

If you are on the Bot plan, the fastest way to feel this click is to head to Settings → Webhooks, point a test subscription at a throwaway Discord channel, subscribe it to `ev_signal`, and watch the first real signal arrive as a JSON POST — then wire the verification snippet above and you are one relay away from production.

API and Webhooks Integration: Wiring Predite Into Your Own Stack | Predite