Developers
Custom webhooks
Receive HMAC-signed alert payloads on your own HTTPS endpoint. Set up in three curl commands. Whale tier only — see /pricing (free during launch).
Whale tier required — even during launch. Webhook endpoints return 403 for Free / Trader / Pro accounts. The launch unlock applies to in-app features, not this server-gated API endpoint, so an active Whale subscription is required to configure webhooks here (admin accounts are auto-Whale).
1
Register your webhook URL
Send a PUT /api/account/webhook with your HTTPS endpoint. You'll get back a secret used for signature verification — save it now, it won't be shown again.
bash
curl -X PUT https://info-hub.io/api/account/webhook \
-H "Cookie: $YOUR_SESSION_COOKIE" \
-H "Content-Type: application/json" \
-d '{"url":"https://my-bot.example.com/infohub-webhook"}'Response:
json
{
"url": "https://my-bot.example.com/infohub-webhook",
"secret": "8f3a9d2b...64-char-hex-string...e1c7",
"createdAt": "2026-05-20T13:42:11.000Z",
"warning": "Save this secret now — it will not be shown again."
}URL restrictions: must be HTTPS, cannot be localhost, private network ranges (10.x, 172.16-31.x, 192.168.x), or cloud metadata hosts (169.254.169.254). SSRF defense.
2
Send a test ping
Fire a synthetic alert.test event to verify your receiver accepts the payload and signature:
bash
curl -X POST https://info-hub.io/api/account/webhook/test \
-H "Cookie: $YOUR_SESSION_COOKIE"Returns { ok: true, message: "Test webhook delivered." } if your endpoint replied 2xx. Otherwise check the message + your server logs.
3
Enable "webhook" on your alerts
Add "webhook" to the channels array when creating or updating an alert rule:
bash
curl -X POST https://info-hub.io/api/account/alerts \
-H "Cookie: $YOUR_SESSION_COOKIE" \
-H "Content-Type: application/json" \
-d '{
"kind": "funding_flip",
"enabled": true,
"channels": ["telegram", "webhook"],
"cooldownMin": 60
}'You can combine
webhook with other channels (telegram, email, browser_push). Each delivers independently with its own cooldown.4
Payload format
Every webhook delivery is a POST with Content-Type: application/json and an HMAC signature header.
http
POST /infohub-webhook HTTP/1.1
Host: my-bot.example.com
Content-Type: application/json
X-InfoHub-Signature: 4a72f0b8c2d...hex-sha256...e1
X-InfoHub-Event: alert.triggered
User-Agent: InfoHub-Webhook/1.0 (+https://info-hub.io)
{
"timestamp": "2026-05-20T13:45:00.000Z",
"version": "v1",
"event": "alert.triggered",
"alerts": [
{
"alertId": "abc-123",
"symbol": "BTC",
"metric": "fundingRate",
"operator": "gt",
"threshold": 0.0001,
"actualValue": 0.00018,
"exchange": "Binance"
}
]
}5
Verify the signature
Compute HMAC-SHA256(secret, raw_body) and compare to X-InfoHub-Signature. Use a constant-time comparison to avoid timing attacks. Same scheme as Stripe / GitHub webhooks.
Node.js (Express)
js
import { createHmac, timingSafeEqual } from 'crypto';
import express from 'express';
const app = express();
const SECRET = process.env.INFOHUB_WEBHOOK_SECRET;
// IMPORTANT: read raw body — JSON.parse before signature check changes the bytes
app.post('/infohub-webhook', express.raw({ type: 'application/json' }), (req, res) => {
const sig = req.header('X-InfoHub-Signature') || '';
const expected = createHmac('sha256', SECRET).update(req.body).digest('hex');
const a = Buffer.from(sig, 'hex');
const b = Buffer.from(expected, 'hex');
if (a.length !== b.length || !timingSafeEqual(a, b)) {
return res.status(401).json({ error: 'invalid signature' });
}
const payload = JSON.parse(req.body.toString('utf8'));
console.log('verified webhook:', payload.event, payload.alerts.length, 'alerts');
res.status(200).json({ ok: true });
});
app.listen(3000);Python (Flask)
python
import hmac
import hashlib
import os
from flask import Flask, request, abort
app = Flask(__name__)
SECRET = os.environ['INFOHUB_WEBHOOK_SECRET'].encode()
@app.route('/infohub-webhook', methods=['POST'])
def webhook():
sig = request.headers.get('X-InfoHub-Signature', '')
expected = hmac.new(SECRET, request.data, hashlib.sha256).hexdigest()
if not hmac.compare_digest(sig, expected):
abort(401)
payload = request.get_json()
print(f"verified webhook: {payload['event']} {len(payload['alerts'])} alerts")
return {'ok': True}, 200
if __name__ == '__main__':
app.run(port=3000)6
Manage your webhook
GET /api/account/webhook — read current URL. Secret is never re-exposed after creation.PUT /api/account/webhook — change URL + rotate secret. Previous secret becomes invalid immediately.DELETE /api/account/webhook — clear config. Not tier-gated — downgrade-friendly.POST /api/account/webhook/test — fire a synthetic alert.test event to your registered URL.7
Delivery behaviour
- 10-second request timeout per delivery.
- Receiver must return
2xxto be considered successful. - No automatic retry today — if your endpoint is down, you miss that batch. Same per-channel cooldown as Discord/email (5-1440 min).
- Multiple alerts firing together arrive in a single POST as
alerts[]— process them as a batch. X-InfoHub-Eventheader tells you the event kind without parsing the body — useful for routingalert.testvsalert.triggereddifferently.