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:

FieldTypeDescription
requestIdstringUnique request identifier
jobIdstringScheduled job ID (for scheduled requests)
typestringscheduled_price, scheduled_ai, or ai_resolution
statusstringcompleted or failed
dataobjectResult data (present when status is completed)
errorobjectError details (present when status is failed)
timestampstringISO 8601 timestamp
signaturestringHMAC-SHA256 signature for verification

Example Payload

Response
{
"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

verify-webhook.ts
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 payload
const { signature, ...payloadWithoutSignature } = payload;
// Compute expected signature
const 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 example
app.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 webhook
console.log('Verified webhook:', payload.type, payload.status);
// Always respond quickly
res.status(200).json({ received: true });
});

Python

verify_webhook.py
import hmac
import hashlib
import json
from flask import Flask, request, jsonify
app = 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 signature
payload_bytes = json.dumps(payload, separators=(',', ':')).encode()
expected = hmac.new(
secret.encode(),
payload_bytes,
hashlib.sha256
).hexdigest()
# Timing-safe comparison
return 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 webhook
print(f"Verified webhook: {payload['type']} - {payload['status']}")
return jsonify({'received': True}), 200

Go

verify_webhook.go
package main
import (
"crypto/hmac"
"crypto/sha256"
"encoding/hex"
"encoding/json"
)
func verifyWebhookSignature(payload map[string]interface{}, secret string) bool {
// Extract and remove signature
signature, ok := payload["signature"].(string)
if !ok {
return false
}
delete(payload, "signature")
// Compute expected signature
payloadBytes, _ := json.Marshal(payload)
mac := hmac.New(sha256.New, []byte(secret))
mac.Write(payloadBytes)
expected := hex.EncodeToString(mac.Sum(nil))
// Timing-safe comparison
return 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:

AttemptDelay
1st retry5 seconds
2nd retry25 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.

Terminal
# Expose your local server with ngrok
ngrok http 3000
# Use the HTTPS URL as your callbackUrl
# https://abc123.ngrok.io/webhook

Related Documentation