Receive your first webhook
Run the sample receiver, publish an article, and see a verified delivery arrive.
On this page
In this tutorial you run a small webhook receiver on your own machine, connect it to Tidenda, and publish an article. At the end, a delivery arrives whose request signature and document signature have both been checked. It takes about twenty minutes and is for developers who have not built a receiver before.
What you need
- The
owneroradminrole in a Tidenda organisation, to create the webhook, and a role that may publish articles in its workflow. - Node.js 22 LTS or later.
- A tunnel tool that gives a local port a public
https://address. Many exist; any of them works. - The deployment's public API host, written
<public-api-host>below. curl, and optionallyjq.
Step 1: Run the sample receiver
Save the Node.js verifier sample as
verify-signature.js in an empty directory. It is a complete
receiver with no dependencies: it checks the request signature,
parses the body and verifies the snapshot signature.
The sample prints nothing when a delivery succeeds. To watch
deliveries arrive, add one line just above the last
res.statusCode = 200 in the request handler:
console.log('accepted', payload.type, doc?.snapshotSignature ? 'snapshot verified' : 'no snapshot')
The receiver needs the platform's public key to verify snapshots.
Fetch the key list and take the publicKey of the entry whose
keyId equals active:
export TIDENDA_SIGNING_KEY=$(curl -s https://<public-api-host>/api/keys \
| jq -r '.active as $a | .keys[] | select(.keyId == $a) | .publicKey')
Without jq, open the URL and copy the value by hand. Then start the
receiver:
node verify-signature.js
It answers on http://localhost:8787/webhook. Send it an unsigned
request from a second terminal:
curl -i -X POST http://localhost:8787/webhook \
-H 'Content-Type: application/json' -d '{}'
The answer is 401 Unauthorized: the receiver turns away anything
without a valid signature. Leave it running.
Step 2: Expose the receiver
The platform can only reach an https:// address on the internet.
Start your tunnel tool for local port 8787, following its own
instructions, and note the public address it prints, such as
https://example-tunnel.test. Your webhook URL is that address with
/webhook added:
https://example-tunnel.test/webhook
Step 3: Create the webhook
In Tidenda, open System settings → Webhooks and choose New webhook. Fill in:
- Name:
My first receiver. - URL: the tunnel address from step 2, ending in
/webhook. - Document type:
core/article. - Statuses: Specific statuses, with only
usableticked.usableis the published status. Leavingdraftout keeps the receiver quiet while you write, since drafts fire on every save.
If no statuses are listed, the organisation has no default workflow
for articles. Choose Any status instead, and expect
document.draft deliveries as well.
Choose Create. The next dialog shows the webhook's signing secret, once only. Copy it, then choose I've saved it.
Stop the receiver with Ctrl+C and start it again with the secret:
TIDENDA_WEBHOOK_SECRET='wh_...' node verify-signature.js
TIDENDA_SIGNING_KEY is still set from step 1. If you opened a new
terminal, export it again.
Step 4: Send a test
Open My first receiver from the webhook list. A new webhook starts disabled, so turn on Enabled first; the switch reads Deliveries active. Then choose Send test.
A notification reports Delivered with HTTP 200, and the receiver's
terminal prints:
accepted webhook.test no snapshot
The test event has no document, so there is nothing to verify beyond the request signature. That check passed, which proves the secret on both sides matches.
If the notification shows 401, the secret was copied incompletely or
the receiver was not restarted. A 404 means the URL lacks /webhook.
A 502 or a timeout usually means the tunnel or the receiver is not
running.
Step 5: Publish an article
Create an article, give it a title and a few sentences of text, and
publish it with the workflow's publish action, which the default
workflow calls Publish. The article moves to usable, and the
platform stores a signed snapshot of it and fires the webhook.
Step 6: See the delivery and its verified snapshot
Within a few seconds the receiver prints:
accepted document.usable snapshot verified
Two checks ran before that line. The request signature proved the
delivery came from the platform with the body unaltered. Then the
receiver decoded document.snapshotBytes, put the prefix
tidenda-snapshot-v1 and a zero byte in front, and verified the
Ed25519 snapshotSignature against the public key fetched in step 1.
A mismatch would have printed ALERT instead, and the event would
have been dropped.
On the webhook's page, choose Refresh. Recent events now
lists a document.usable entry marked success, with the article's
id and version and one attempt answered with HTTP 200. Expanding it
shows the attempt's duration and response.
If the terminal says TIDENDA_SIGNING_KEY not set, the snapshot was
not checked: export the key as in step 1 and restart the receiver.
To see deduplication matter, note the event id in the log entry. Every retry or replay of this event would carry the same id, which is how a production receiver recognises repeats. The sample does not deduplicate; Implementing a webhook receiver shows how.
Step 7: Clean up
On the webhook's page, turn off Enabled, or choose Revoke webhook to remove it for good. Then stop the tunnel and the receiver. A webhook left pointing at a dead tunnel fails each event until it runs out of retries.
What you built
You built a receiver that:
- accepts only requests signed with the webhook's secret and a fresh timestamp;
- verifies that a published article's content is exactly what the platform signed, using a public key anyone can fetch;
- answers quickly, so the platform records each delivery as a success.
A production receiver adds deduplication on the event id, a job
queue for slow work, and a cached key list that refreshes on an
unknown keyId.