Webhooks
Receive real-time notifications when scheduled jobs complete or AI resolutions finish processing.
Overview
When you schedule a price check or AI resolution, you provide a callbackUrl. Once the job completes, we send a POST request to your URL with the results.
Requirements:
- Callback URL must use HTTPS (HTTP not allowed)
- URL must be publicly accessible (no localhost or private IPs)
- Your server must respond within 30 seconds
- Return a 2xx status code to acknowledge receipt
Webhook Payload
All webhooks are sent as POST requests with a JSON body containing the following fields:
| Field | Type | Description |
|---|---|---|
| requestId | string | Unique request identifier |
| jobId | string | Scheduled job ID (for scheduled requests) |
| type | string | scheduled_price, scheduled_ai, or ai_resolution |
| status | string | completed or failed |
| data | object | Result data (present when status is completed) |
| error | object | Error details (present when status is failed) |
| timestamp | string | ISO 8601 timestamp |
| signature | string | HMAC-SHA256 signature for verification |
Example Payload
{"requestId": "req_abc123def456","jobId": "job_xyz789","type": "scheduled_price","status": "completed","data": {"symbol": "SOL","vwapPrice": 185.42,"sources": ["birdeye", "coingecko", "jupiter"],"timestamp": "2026-01-17T12:00:00.000Z","costUsd": "0.05"},"timestamp": "2026-01-17T12:00:01.000Z","signature": "a1b2c3d4e5f6..."}
Verifying Webhook Signatures
Every webhook includes an HMAC-SHA256 signature to verify it came from Predikt. You should always verify signatures before processing webhook data.
Security Warning: Never process webhook data without verifying the signature. Attackers could send fake webhooks to your endpoint.
How Signatures Work
1. We create a payload object containing all fields except the signature
2. We JSON-stringify the payload (compact format, no spaces) and compute HMAC-SHA256 using your account's webhook secret
3. The signature is included in both the payload body and the X-Predikt-Signature header
Important: The JSON must be serialized in compact format (no extra spaces). In JavaScript use JSON.stringify(payload), in Python use json.dumps(payload, separators=(',', ':')).
Getting Your Webhook Secret
Each API account has a unique webhook secret that is automatically generated when you create your account. You can find your secret in the API Dashboard under the "Webhook Secret" section. Keep this secret secure and never expose it in client-side code.
Your secret is unique: Each account has its own 64-character hex secret. This allows you to verify that webhooks are genuinely from Predikt and intended for your account.
Verification Examples
Node.js / TypeScript
import crypto from 'crypto';interface WebhookPayload {requestId: string;jobId?: string;type: string;status: 'completed' | 'failed';data?: unknown;error?: { code: string; message: string };timestamp: string;signature: string;}function verifyWebhookSignature(payload: WebhookPayload,secret: string): boolean {// Extract signature from payloadconst { signature, ...payloadWithoutSignature } = payload;// Compute expected signatureconst hmac = crypto.createHmac('sha256', secret);hmac.update(JSON.stringify(payloadWithoutSignature));const expectedSignature = hmac.digest('hex');// Compare signatures (timing-safe)return crypto.timingSafeEqual(Buffer.from(signature),Buffer.from(expectedSignature));}// Express.js exampleapp.post('/webhook', (req, res) => {const payload = req.body as WebhookPayload;const secret = process.env.PREDIKT_WEBHOOK_SECRET!;if (!verifyWebhookSignature(payload, secret)) {console.error('Invalid webhook signature');return res.status(401).json({ error: 'Invalid signature' });}// Process the verified webhookconsole.log('Verified webhook:', payload.type, payload.status);// Always respond quicklyres.status(200).json({ received: true });});
Python
import hmacimport hashlibimport jsonfrom flask import Flask, request, jsonifyapp = Flask(__name__)WEBHOOK_SECRET = os.environ.get('PREDIKT_WEBHOOK_SECRET')def verify_webhook_signature(payload: dict, secret: str) -> bool:"""Verify the HMAC-SHA256 signature of a webhook payload."""signature = payload.pop('signature', None)if not signature:return False# Compute expected signaturepayload_bytes = json.dumps(payload, separators=(',', ':')).encode()expected = hmac.new(secret.encode(),payload_bytes,hashlib.sha256).hexdigest()# Timing-safe comparisonreturn hmac.compare_digest(signature, expected)@app.route('/webhook', methods=['POST'])def handle_webhook():payload = request.get_json()if not verify_webhook_signature(payload.copy(), WEBHOOK_SECRET):return jsonify({'error': 'Invalid signature'}), 401# Process the verified webhookprint(f"Verified webhook: {payload['type']} - {payload['status']}")return jsonify({'received': True}), 200
Go
package mainimport ("crypto/hmac""crypto/sha256""encoding/hex""encoding/json")func verifyWebhookSignature(payload map[string]interface{}, secret string) bool {// Extract and remove signaturesignature, ok := payload["signature"].(string)if !ok {return false}delete(payload, "signature")// Compute expected signaturepayloadBytes, _ := json.Marshal(payload)mac := hmac.New(sha256.New, []byte(secret))mac.Write(payloadBytes)expected := hex.EncodeToString(mac.Sum(nil))// Timing-safe comparisonreturn hmac.Equal([]byte(signature), []byte(expected))}
Retry Policy
If your endpoint fails to respond with a 2xx status code, we'll retry with exponential backoff:
| Attempt | Delay |
|---|---|
| 1st retry | 5 seconds |
| 2nd retry | 25 seconds |
| 3rd retry (final) | 125 seconds |
After 3 failed attempts, the webhook is marked as failed. You can check the status of your scheduled jobs via the Scheduled Jobs API.
Best Practices
Respond Quickly
Return a 200 response immediately, then process the webhook asynchronously. Long processing times may cause timeouts and retries.
Handle Duplicates
Use the requestId or jobId to deduplicate webhooks. Retries may result in the same webhook being delivered multiple times.
Verify Signatures
Always verify the HMAC signature before processing any webhook data. This prevents attackers from sending fake webhooks to your endpoint.
Use HTTPS
We only send webhooks to HTTPS endpoints. Ensure your SSL certificate is valid and not expired.
Testing Webhooks
Use tools like webhook.site or ngrok to test webhooks during development.
# Expose your local server with ngrokngrok http 3000# Use the HTTPS URL as your callbackUrl# https://abc123.ngrok.io/webhook