Any service that can send a signed POST can start work on the node, with no server of yours in between. The routine has one webhook trigger with verify.scheme: b3os and two steps: vision/scrape@1 reads the URL the delivery names, and infer/chat@1 says in one sentence what the page offers. Publishing answers with the hook's URL and its signing secret, once; the sender signs "<unix seconds>.<raw body>" with HMAC-SHA256 and sends it as B3OS-Signature: t=<seconds>,v1=<hex>. The hook URL takes no API key: the signature is the authentication.

The recipe also checks the two things a sender's retries and a stranger's guesses run into: the same delivery sent again is answered with the first run's ID and starts nothing (deduplicated: true), and a delivery signed with any other secret is refused with 401 webhook_signature_invalid. The hook's delivery log lists all three.

yaml
triggers: - type: webhook id: inbound verify: scheme: b3os with: url: ${{ trigger.body.url }}
Check

Verified 2026-11-01: this recipe's last run passed against https://beta-api.b3os.org, label live-recorded (recorded from production), from commit 771a9508. The recipe harness runs it again, and a failed run takes this page out of the cookbook.

Run it

bash
API=https://beta-api.b3os.orgAUTH="Authorization: Bearer $B3OS_API_KEY"NAME=webhook-briefcurl -s -X PUT "$API/v1/routines/$NAME" -H "$AUTH" -H "Content-Type: application/json" -d '{ "name": "webhook-brief", "inputs": {"type": "object", "required": ["url"], "properties": {"url": {"type": "string"}}}, "triggers": [{"type": "webhook", "id": "inbound", "verify": {"scheme": "b3os"}, "with": {"url": "${{ trigger.body.url }}"}}], "steps": [ {"id": "page", "uses": "vision/scrape@1", "with": {"url": "${{ inputs.url }}", "formats": ["markdown"]}}, {"id": "brief", "uses": "infer/chat@1", "needs": ["page"], "with": { "model": "b3os/qwen3.5-9b", "max_tokens": 120, "temperature": 0, "chat_template_kwargs": {"enable_thinking": false}, "messages": [ {"role": "system", "content": "In one plain sentence of at most 30 words, say what this web page offers."}, {"role": "user", "content": "${{ steps.page.outputs.markdown }}"}]}}], "output": {"brief": "${{ steps.brief.outputs.choices[0].message.content }}"}}' > published.jsonHOOK_URL=$(jq -r '.triggers[0].hook.url' published.json)HOOK_SECRET=$(jq -r '.triggers[0].hook.secret' published.json)BODY='{"url": "https://beta.b3os.org/demo/pricing/"}'T=$(date +%s)SIG=$(printf '%s.%s' "$T" "$BODY" | openssl dgst -sha256 -hmac "$HOOK_SECRET" | sed 's/^.* //')RUN=$(curl -s "$HOOK_URL" -H "Content-Type: application/json" -H "B3OS-Signature: t=$T,v1=$SIG" -d "$BODY" \ | jq -r .run_id)sleep 2curl -s "$API/v1/routines/runs/$RUN" -H "$AUTH" | jq '{status, result}'curl -s -X DELETE "$API/v1/routines/$NAME" -H "$AUTH" > /dev/null

delivery_to_done_ms below is the client's view, from sending the delivery to reading the finished run, polled every 100 ms through the edge; run_node_ms is the same run by its own timestamps on the node. The secret is shown once: store it where the sender keeps secrets, and rotate it with POST /v1/routines/{routine}/hooks/{hook_id}/rotate.

Owner: Docs. Products: Docs, Routines, Vision, Infer.

Output of the last green run

bash
$ node run.mjs# exit 0

The outputs this recipe publishes (its recipe.json publish.outputs), with workspace identifiers redacted. The run's stdout, stderr and other outputs are not published.

Captured 2026-11-01 from commit 771a9508 , label live-recorded (recorded from production). Case recipes/signed-webhook-brief .

Ask a question... ⌘I