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

  1. Sign in to AlgoLens and open Developer settings Webhooks.
  2. Enter the destination URL.
  3. Tick the events you want to receive.
  4. Select Create webhook and copy the signing secret. It starts with whsec_.
RuleDetail
ProtocolThe URL must use https://.
HostLocal 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.
EventsSelect at least one. Unknown event names are rejected. Duplicates are ignored.
RevokingRevoke 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:

HeaderValue
Content-Typeapplication/json
X-AlgoLens-EventThe event name, for example call.high.
X-AlgoLens-Signaturet=<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 ) )
  1. Read the header and split it into t and s.
  2. Compute the HMAC over t, . and the raw body bytes with your secret.
  3. Compare with s using a constant-time comparison.
  4. Reject the request if t is 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

TopicWhat happens
SuccessAny 2xx response counts as delivered. Respond quickly and do slow work after acknowledging.
TimeoutAlgoLens waits 5 seconds for a response. A slower response is recorded as a failed delivery.
RetriesThere are no automatic retries. A failed delivery is recorded once and is not sent again.
RepeatsThe 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.
OrderingEvents are sent independently and may arrive out of order. Use the timestamp and the values inside data to order them.
TimingEvents are raised as calls are re-priced, which happens on the platform scan cycle of about 30 seconds. They are not tick-by-tick.
HistoryThe dashboard keeps the last 100 delivery attempts with their HTTP status. Request payloads and your response bodies are not stored.

3.7Troubleshooting

SymptomLikely cause
Nothing arrivesThe 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 creationIt is not HTTPS, or the host is local or private.
History shows Failed with no codeThe 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 / 5xxYour 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 matchesThe 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