Skip to main content
In about five minutes you will run a receiver on your machine, give it a public HTTPS address, subscribe it to a busy Testnet account and watch signed events arrive and pass verification. You need curl, jq, a tunnel tool (cloudflared or ngrok) and one of Node.js 18+, Python 3.9+ or Go 1.22+. The Free plan includes one Testnet endpoint, and Testnet deliveries cost no credits.
1

Create a webhook key

Open app.lumeus.dev, select Testnet, then go to Webhooks > Webhook keys and choose Create webhook key. The key starts with whk_ and is shown once. Webhook keys belong to one network, and lum_ API keys do not work here.Set these in your first terminal. The host selects the network: webhooks-testnet.lumeus.dev is Testnet, webhooks.lumeus.dev is Mainnet.
2

Download a receiver

Each receiver listens on port 3000 (set PORT to change it), verifies the signature and the signed delivery context, ignores duplicates and answers 204. Download one into a new folder:
3

Start the receiver

Start the receiver now, before the subscription exists, so the first live events find it running. Open a second terminal in the lumeus-webhook folder:
It prints listening on http://localhost:3000 and keeps running. It reads the signing secret from webhook-secret.txt on every request; until the create step writes that file, it answers 503.
4

Expose port 3000 over HTTPS

Lumeus delivers only to public https:// URLs, so open a tunnel in a third terminal and leave it running:
Back in the first terminal, save the printed URL. A new tunnel can take up to a minute before its name resolves; repeat this curl until it prints 503 (your receiver, reached through the tunnel, without a secret yet) instead of 000:
5

Create the subscription

Subscribe to transfers of the sample account. The response contains the signing secret (hmac_secret) only this once. Pick one idempotency key for this create and keep it for any retry. On Linux without uuidgen, the command falls back to /proc/sys/kernel/random/uuid (or use python3 -c 'import uuid; print(uuid.uuid4())'):
Then create the subscription. The command writes the secret to webhook-secret.txt only on 201 or 202, and never overwrites an existing secret file:
Response (201, example values, secret removed)
If the request fails, subscription.json holds the error: 400 for a URL that is not public HTTPS or does not resolve yet, or for an unknown body field (the host already selects the network, so there is no network field), and 403 if your organization already has its Free endpoint (delete it or upgrade). Fix the cause and run the same command again with the same CREATE_KEY. A retry of a create that already succeeded returns 200 with the same subscription and no secret; if you lost the secret, rotate the signing key.
6

Wait until the endpoint is active

Live matching starts once effective_state is active. Free and paid endpoints are active in the 201 create response, so you can skip this step. If yours shows activating (the create returned 202), poll until it changes. A test sent while it is activating returns 409 with Retry-After: 5.
7

Send a test event

A test sends one signed webhook.test event to your receiver and reports what it answered. It needs the subscription’s current ETag in If-Match:
Response (200, example values)
status is delivered when your receiver answered 2xx. Otherwise it is rejected with the response_code or a last_error such as receiver timed out, or uncertain if the outcome was lost (read it again at the Location URL). The receiver prints:
8

Watch live events

The sample account sends Testnet XLM all the time, so live transfers start arriving within a minute:
Each line is a delivery whose signature and signed context passed verification. To see what failed instead, list recent deliveries:
9

Clean up

The sample account is busy: about 335 matching transfers an hour, so it uses the Free plan’s 1,000 deliveries a day in about 3 hours. After that, the endpoint pauses until 00:00 UTC (see Plans). Delete the subscription when you are done; this also frees your Free endpoint:
Stop the receiver and the tunnel with Ctrl+C.

Next