API guide 3
Webhooks
Receive your call events at your own HTTPS endpoint the moment they happen, signed so you can prove they came from AlgoLens.
3.1How webhooks work
A webhook is an HTTPS POST that AlgoLens sends to a URL you control whenever something changes on one of your calls: a new call, a new high, a move between the Strong, Watch and Bad lanes, and so on. The full list is in Webhook events.
- Webhooks are an Edge feature. Delivery checks your plan every time, so a downgrade stops deliveries automatically.
- You only ever receive events for your own calls.
- While you have at least one active webhook, AlgoLens keeps tracking your calls even when the dashboard is closed, so events keep arriving.
- Endpoints are managed in the dashboard, under
Developer settings.
3.2Create an endpoint
- Sign in to AlgoLens and open
Developer settings→Webhooks. - Enter the destination URL.
- Tick the events you want to receive.
- Select
Create webhookand copy the signing secret. It starts withwhsec_.
| Rule | Detail |
|---|---|
| Protocol | The URL must use https://. |
| Host | Local and private addresses are rejected, such as localhost, 127.x, 10.x, 172.16–31.x, 192.168.x, 169.254.x and .local names. Use a tunnel with a public HTTPS address to test from your machine. |
| Events | Select at least one. Unknown event names are rejected. Duplicates are ignored. |
| Revoking | Revoke an endpoint from the same page. It stops receiving events immediately and cannot be re-enabled. Create a new one instead. |
3.3The request we send
Every delivery is a POST with a JSON body and these headers:
| Header | Value |
|---|---|
Content-Type | application/json |
X-AlgoLens-Event | The event name, for example call.high. |
X-AlgoLens-Signature | t=<unix seconds>,s=<hex signature> |
POST /algolens HTTP/1.1
Content-Type: application/json
X-AlgoLens-Event: call.high
X-AlgoLens-Signature: t=1788000000,s=9f2c…e41b
{"event":"call.high","data":{ ... },"timestamp":1788000000}The body always has three keys: event, data (the call) and timestamp (Unix seconds, the same value as t in the signature header). The shape of data is described in Webhook events.
3.4Verify the signature
Always verify a request before acting on it. The signature is an HMAC-SHA256 of the timestamp, a full stop and the raw request body, keyed with your signing secret:
signature = hex( HMAC_SHA256( secret, t + "." + raw_body ) )- Read the header and split it into
tands. - Compute the HMAC over
t,.and the raw body bytes with your secret. - Compare with
susing a constant-time comparison. - Reject the request if
tis too old. Five minutes is a sensible tolerance. AlgoLens does not enforce one, so this check is yours to make and it protects you against replayed requests.
Verify a webhook
// npm install express
import crypto from "node:crypto";
import express, { type Request, type Response } from "express";
const app = express();
const SECRET = process.env.ALGOLENS_WEBHOOK_SECRET ?? "";
// Read the raw body: the signature covers the exact bytes we sent. Do not put
// a global app.use(express.json()) before this route, or the body arrives
// already parsed and every signature fails.
app.post(
"/algolens",
express.raw({ type: "application/json" }),
(req: Request, res: Response) => {
const header = req.get("X-AlgoLens-Signature") ?? "";
const parts: Record<string, string> = Object.fromEntries(
header.split(",").map((part) => part.split("=") as [string, string]),
);
const rawBody = (req.body as Buffer).toString("utf8");
const expected = crypto
.createHmac("sha256", SECRET)
.update(parts.t + "." + rawBody)
.digest("hex");
const received = Buffer.from(parts.s ?? "", "hex");
const valid =
received.length === 32 &&
crypto.timingSafeEqual(received, Buffer.from(expected, "hex"));
const fresh = Math.abs(Date.now() / 1000 - Number(parts.t)) <= 300;
if (!valid || !fresh) return res.sendStatus(400);
const { event, data } = JSON.parse(rawBody);
// Handle the event, then acknowledge quickly.
res.sendStatus(200);
},
);
app.listen(3000);3.5Test your endpoint
There is no test button, so exercise your endpoint by sending it a request signed the same way AlgoLens signs real ones. Use any secret you like locally, as long as your handler is configured with the same value.
Send a signed test request
SECRET="whsec_your_signing_secret"
URL="http://localhost:3000/algolens"
T=$(date +%s)
BODY='{"event":"call.watch","data":{"id":"test-call","symbol":"TEST","currentMultiple":1.2,"lane":"watch","previousLane":"bad"},"timestamp":'$T'}'
S=$(printf '%s' "$T.$BODY" | openssl dgst -sha256 -hmac "$SECRET" -hex | sed 's/^.* //')
curl -i -X POST "$URL" \
-H "Content-Type: application/json" \
-H "X-AlgoLens-Event: call.watch" \
-H "X-AlgoLens-Signature: t=$T,s=$S" \
--data "$BODY"A 200 means your verification passed. Change one character of the body or the secret and you should get a 400 instead.
3.6Delivery behaviour
| Topic | What happens |
|---|---|
| Success | Any 2xx response counts as delivered. Respond quickly and do slow work after acknowledging. |
| Timeout | AlgoLens waits 5 seconds for a response. A slower response is recorded as a failed delivery. |
| Retries | There are no automatic retries. A failed delivery is recorded once and is not sent again. |
| Repeats | The same kind of event can legitimately fire more than once for a call, for example a new high on every fresh peak or a lane event each time a call crosses a boundary. Make your handler safe to run twice. |
| Ordering | Events are sent independently and may arrive out of order. Use the timestamp and the values inside data to order them. |
| Timing | Events are raised as calls are re-priced, which happens on the platform scan cycle of about 30 seconds. They are not tick-by-tick. |
| History | The dashboard keeps the last 100 delivery attempts with their HTTP status. Request payloads and your response bodies are not stored. |
3.7Troubleshooting
| Symptom | Likely cause |
|---|---|
| Nothing arrives | The plan is not Edge, the endpoint was revoked, or the event was not ticked when the endpoint was created. Check the endpoint list and the History tab. |
| URL rejected on creation | It is not HTTPS, or the host is local or private. |
History shows Failed with no code | The request timed out or could not connect. Confirm the endpoint is reachable from the public internet and answers within 5 seconds. |
History shows Failed 4xx / 5xx | Your server rejected or errored on the request. A 4xx from signature verification usually means the body was parsed before it was checked, or the wrong secret is in use. |
| Signature never matches | The raw body was modified before hashing (for example by a global JSON parser that runs first), the secret belongs to a different endpoint, or the header was split incorrectly. Each endpoint has its own secret. |
AlgoLens developer documentationAPI guide 3 · Webhooks