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:
| Variable | Description |
|---|---|
WIDGET_BASE_URL | Base URL of the prediction widget iFrame |
PLATFORM_HMAC_SECRET | Shared secret for verifying X-Payload-Signature headers |
OPERATOR_ID | Your 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.
| Endpoint | When Called | Purpose |
|---|---|---|
POST /v1/withdrawals | Bet placement | Debit stake from bettor balance |
POST /v1/deposits | Settlement | Credit payout to bettor balance |
DELETE /v1/rollbacks/{id} | Failed bet compensation | Reverse a prior withdrawal |
Request Shape
Every wallet call carries these headers:
| Header | Value |
|---|---|
X-Payload-Signature | HMAC-SHA256 hex of the raw request body |
X-Timestamp | ISO 8601 UTC timestamp |
X-Nonce | UUID v4, unique per request |
Content-Type | application/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();
}
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:
| Claim | Type | Description |
|---|---|---|
sub | string | Bettor UUID in your system |
currency | string | ISO 4217 currency code (e.g., "USD") |
iss | string | Your operator ID (OPERATOR_ID) |
iat | number | Issued-at timestamp (Unix) |
exp | number | Expiry 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/withdrawalsimplemented and idempotent -
POST /v1/depositsimplemented 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/sessionmints 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_idon each page render - Tested end-to-end with the sandbox environment provided during onboarding
See Also
- Wallet Endpoints — full request/response schemas, rollback rules, deposit amount table
- Authentication Flow — two-JWT architecture, sequence diagram, key points
- Widget Embedding — URL parameters, responsive sizing, CSP requirements
- Standalone vs Bundled — comparison of integration paths