Skip to main content

Quick Start: Standalone Integration

This guide takes you from zero to a live prediction markets integration. You will implement the wallet endpoints your server must expose, verify HMAC signatures on platform requests, set up the auth session flow, and embed the widget in your frontend.

Follow the steps in order — each section builds on the previous.

Prerequisites

Before you start, confirm you have:

  • A server reachable over HTTPS that the platform can call for wallet operations
  • A shared HMAC secret provisioned by your account manager (used to verify platform requests)
  • A bettor identity system — you must be able to look up a bettor by their ID and debit or credit their balance
  • A JWKS endpoint or RSA key pair for signing session JWTs (RS256)
  • The following values from your onboarding pack:
VariableDescription
WIDGET_BASE_URLBase URL of the prediction widget iFrame
PLATFORM_HMAC_SECRETShared secret for verifying X-Payload-Signature headers
OPERATOR_IDYour operator identifier, included in session JWT claims

Step 1: Implement Wallet Endpoints

The platform makes three server-to-server calls to your backend during the bet lifecycle. You must implement all three.

EndpointWhen CalledPurpose
POST /v1/withdrawalsBet placementDebit stake from bettor balance
POST /v1/depositsSettlementCredit payout to bettor balance
DELETE /v1/rollbacks/{id}Failed bet compensationReverse a prior withdrawal

Request Shape

Every wallet call carries these headers:

HeaderValue
X-Payload-SignatureHMAC-SHA256 hex of the raw request body
X-TimestampISO 8601 UTC timestamp
X-NonceUUID v4, unique per request
Content-Typeapplication/json

Example: Withdrawal

curl -X POST https://your-backend.example.com/v1/withdrawals \
-H "Content-Type: application/json" \
-H "X-Payload-Signature: 37f9186da8bef5457f94d56d1c76dc37..." \
-H "X-Timestamp: 2024-03-04T12:00:00Z" \
-H "X-Nonce: a1b2c3d4-e5f6-7890-abcd-ef1234567890" \
-d '{
"transaction_id": "txn_01HZABC...",
"bettor_id": "bettor_42",
"amount": "10.50",
"currency": "USD",
"metadata": { "market_id": "mkt_..." }
}'

Expected response (HTTP 200):

{
"transaction_id": "your-internal-txn-id",
"balance": "489.50"
}

Idempotency

The platform may retry wallet calls on network timeouts. Your endpoints must be idempotent — if you receive a request with a transaction_id you have already processed, return the same response you returned the first time. Do not debit or credit the bettor a second time.

For full request/response schemas and deposit amount rules, see the Wallet Endpoints reference.

Step 2: Verify HMAC Signatures

Every wallet call includes an X-Payload-Signature header. Verify this before processing any request.

Algorithm

signature = HMAC-SHA256(raw_request_body, PLATFORM_HMAC_SECRET)
expected = hex_encode(signature)
valid = timing_safe_equal(expected, X-Payload-Signature)

Use a timing-safe comparison to prevent timing attacks.

Node.js Middleware

const crypto = require('crypto');

function verifyPlatformRequest(req, res, next) {
const signature = req.headers['x-payload-signature'];
const timestamp = req.headers['x-timestamp'];

if (!signature || !timestamp) {
return res.status(401).json({ error: 'Missing signature headers' });
}

// Reject requests older than 5 minutes
const age = Date.now() - new Date(timestamp).getTime();
if (age > 5 * 60 * 1000) {
return res.status(401).json({ error: 'Request timestamp too old' });
}

// Verify HMAC
const expected = crypto
.createHmac('sha256', process.env.PLATFORM_HMAC_SECRET)
.update(req.rawBody) // use raw body, not parsed JSON
.digest('hex');

const valid = crypto.timingSafeEqual(
Buffer.from(expected),
Buffer.from(signature),
);

if (!valid) {
return res.status(401).json({ error: 'Invalid signature' });
}

next();
}
Raw body required

Sign and verify against the raw request bytes before JSON parsing. If your framework parses the body first, configure it to preserve the original buffer (e.g., Express verify option).

For Go and Python examples, see Wallet Endpoints — Verification Examples.

Step 3: Implement the Auth Session Flow

The widget uses a one-time session_request_id to obtain a JWT. Your backend must implement two pieces: generating session request IDs and exchanging them for JWTs.

How It Works

1. Bettor opens your platform
2. Your backend generates a short-lived session_request_id (1–2 min TTL, single-use)
3. Your page loads the widget iFrame: WIDGET_BASE_URL?session_request_id=<id>
4. Widget calls POST /v1/session on your backend with the session_request_id
5. Your backend validates it, mints a JWT, marks it used
6. Widget receives the JWT and uses it for all API calls

Session Endpoint

The widget calls POST /v1/session on your backend. Implement this endpoint:

# Widget sends:
POST /v1/session
Content-Type: application/json

{ "session_request_id": "sreq_01HZABC..." }
# Your backend responds:
HTTP 200
{
"auth_token": "<RS256 JWT>",
"expires_at": "2024-03-05T12:00:00Z"
}

JWT Claims

Mint the JWT with RS256 using your RSA private key. Include these claims:

ClaimTypeDescription
substringBettor UUID in your system
currencystringISO 4217 currency code (e.g., "USD")
issstringYour operator ID (OPERATOR_ID)
iatnumberIssued-at timestamp (Unix)
expnumberExpiry timestamp (Unix)

Example:

const jwt = require('jsonwebtoken');

function mintSessionJWT(bettorId, currency) {
return jwt.sign(
{
sub: bettorId,
currency: currency,
iss: process.env.OPERATOR_ID,
},
process.env.JWT_RSA_PRIVATE_KEY,
{
algorithm: 'RS256',
expiresIn: '24h',
},
);
}

JWKS Endpoint

The platform gateway validates incoming JWTs against your public key. You must expose a JWKS endpoint at a stable URL and provide that URL during onboarding. The endpoint must:

  • Serve your RSA public key in JWK Set format
  • Be publicly reachable without authentication
  • Return Content-Type: application/json

Most JWT libraries include JWKS helpers — for example, jose (Node.js), jwx (Go), or python-jose.

For the full auth architecture including sequence diagram, see the Authentication Flow reference.

Step 4: Embed the Widget

With wallet endpoints and session flow in place, embed the widget on your platform page.

Basic iFrame

<!-- In your page template, after generating session_request_id server-side -->
<iframe
src="https://widget.predictionmarkets.io?session_request_id=SREQ_ID&locale=en&theme=dark"
width="100%"
height="600"
frameborder="0"
allow="clipboard-write"
title="Prediction Markets"
></iframe>

Replace SREQ_ID with the session_request_id your backend generated for this session. Never reuse a session_request_id across page loads.

The widget is fully customizable to match your platform branding — colors, typography, layout, and theme are configured during onboarding. For details, see Widget Embedding.

Responsive Sizing

.predictions-widget-container {
position: relative;
width: 100%;
max-width: 1200px;
min-height: 400px;
}

.predictions-widget-container iframe {
width: 100%;
height: 100%;
min-height: 400px;
border: none;
}

For URL parameters, responsive CSS details, and security considerations, see the Widget Embedding reference.

Go-Live Checklist

Run through this before enabling predictions for real bettors:

  • POST /v1/withdrawals implemented and idempotent
  • POST /v1/deposits implemented and handles zero-amount deposits (lost bets)
  • DELETE /v1/rollbacks/{id} implemented and credits bettor back
  • HMAC signature verification active on all three wallet endpoints
  • Timestamp validation rejects requests older than 5 minutes
  • Nonce deduplication in place (optional but recommended)
  • POST /v1/session mints RS256 JWT with correct claims (sub, currency, iss)
  • Session request IDs are single-use and expire after 1–2 minutes
  • JWKS endpoint is live and URL provided to your account manager
  • Widget iFrame loads with fresh session_request_id on each page render
  • Tested end-to-end with the sandbox environment provided during onboarding

See Also