Stridee
Stridee Docs

Quickstart

Receive and open your first webhook delivery on localhost, then point it at production.

You need an account and the CLI signed in. Fifteen minutes, no public URL, and no shared secret at any point.

1. Start listening

Shell
stridee listen --forward-to localhost:3000/webhooks

For as long as it runs, this is one more webhook endpoint on your account, and every delivery is forwarded to your local server exactly as production receives it — same sealed body, same headers. On first run it makes an encryption key for this machine and tells you where the private half is:

Text
Made a webhook key for this machine. Point your handler's decrypt at:
  ~/.config/stridee/listen-0b6f2d14.pem

2. Write the handler

What arrives is a small cleartext envelope with the ciphertext in enc. It is standard JWE compact, so opening it is a library call rather than homegrown crypto.

import { readFileSync } from 'node:fs';
import { homedir } from 'node:os';
import { join } from 'node:path';
import { createPrivateKey } from 'node:crypto';
import { compactDecrypt } from 'jose';

const key = createPrivateKey(readFileSync(join(homedir(), '.config/stridee/listen-0b6f2d14.pem')));

export async function handle(rawBody, res) {
  const { id, enc } = JSON.parse(rawBody);

  const { plaintext } = await compactDecrypt(enc, key);
  const event = JSON.parse(new TextDecoder().decode(plaintext));

  console.log(event.type, id);

  // The nonce came out of the ciphertext, so returning it proves you opened it.
  res.json({ nonce: event.nonce });
}

Return a 2xx carrying {"nonce": …}. That echo is the difference between us knowing a server answered and knowing your service opened the body — see Confirming a delivery. Before production, add the signature check and dedupe — the full order is in Handling a delivery.

3. Send a ping

In another terminal:

Shell
stridee trigger ping

It goes to your running listeners only, never to production endpoints, and prints how your handler answered. Inside the envelope, your handler sees:

The plaintext inside a ping
{
  "id": "9f2c1e7a-4b83-4d21-9a6e-3c5f0d8b71a4",
  "type": "ping",
  "created": "2026-08-04T06:14:02Z",
  "webhook_id": "2d7b45c1-8e0a-4f36-b512-9c7d3a6e04f8",
  "nonce": "Kd4nWpLbEa9xTvRm2Cj7Lz0Bq2vNhCz7",
  "data": {}
}

Nothing about it is a special case: it takes exactly the path a real event takes.

4. Go live

Once the handler is deployed, give production its own key and endpoint.

  1. Add a key on Keys. The pair is generated in your browser and the private half downloads straight to disk — confirm the download when asked, because a key whose private half may not exist anywhere cannot be assigned to an endpoint. Point your deployed handler at that file.
  2. Add your endpoint on Webhooks and choose that key. The URL has to be https:// and resolve to a public address. There is no verification step, and no secret comes back.
  3. Press Send ping. It shows the status code and timing, or which hop failed, and the ping appears on the event stream with every other event.

Prefer to mint the key yourself? It has to be X25519:

Bring your own
openssl genpkey -algorithm x25519 -out stridee-enc.pem
openssl pkey -in stridee-enc.pem -pubout

Webhooks covers the handler start to finish, and Encrypted deliveries covers the scheme and rotating a key without a gap.

Something wrong or missing on this page? Tell us in Discord. Need something the API doesn’t do yet? Request it on the roadmap.