Tidenda

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 owner or admin role 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 optionally jq.

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 usable ticked. usable is the published status. Leaving draft out 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.