Quickstart
Create an encryption key, register an endpoint, and open your first delivery.
Fifteen minutes, and no shared secret at any point.
1. Create an encryption key
Open Keys in the console and add a key. The pair is generated in your browser: the private half downloads straight to disk and never reaches us, and the row that appears on the screen is the public half.
It has to be X25519 — that is the key-agreement curve, and it is what we seal deliveries to. If you would rather mint it yourself:
openssl genpkey -algorithm x25519 -out stridee-enc.pem
openssl pkey -in stridee-enc.pem -puboutConfirm the download when the console asks. A key whose download was never confirmed cannot be assigned to an endpoint — otherwise a closed tab leaves a registered public key whose private half no longer exists anywhere, and every delivery sealed to it would be unreadable.
2. Register an endpoint
Add your URL on the Webhooks screen and choose the key from step 1. It has to
be https:// and it has to resolve to a public address.
There is no verification step to wait through, and no secret comes back from this step.
3. Send a ping
Press Send ping. It mints a real ping event, seals it to your key, POSTs it to your
URL and shows you what came back — the status code and how fast, or which hop failed.
{
"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, which is the point: it exercises exactly the path a real event takes.
4. Open it
What lands on the wire is a small cleartext envelope with the ciphertext in enc. Decrypt
it with the private key you downloaded — it is standard JWE compact, so this is three lines
and a library rather than homegrown crypto.
import { readFileSync } from 'node:fs';
import { createPrivateKey } from 'node:crypto';
import { compactDecrypt } from 'jose';
const key = createPrivateKey(readFileSync('stridee-5a8f31d6-0c94-4b27-a3e5-71fd2809bc4e.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": …} and you are done. That echo is the difference
between us knowing a server answered and knowing your service opened the body — a handler
that returns a bare 200 on a ciphertext it could not decrypt would otherwise look
perfectly healthy in the console. See Confirming a
delivery.
5. Watch it arrive
The ping shows up on the event stream like any other event, with how many endpoints it went to and how many accepted it. Every provider upload on every connected account lands there too.
Webhooks covers the handler start to finish, and Encrypted deliveries covers the scheme and how to rotate a key without a gap.
Something wrong or missing on this page? Tell us in Discord.