Receive a webhook
A signed event on your laptop, without deploying anything.
You do not need a public URL, a tunnel service, or a deployed environment to start. The CLI streams live sandbox events to your machine and signs them the same way production does, so the handler you write here is the handler you ship.
Write the endpoint
It has to do three things in this order: verify, acknowledge, then work.
# The request your endpoint will receive
POST /webhooks/wajub HTTP/1.1
X-Wajub-Signature: v1=9f86d081884c7d659a2feaa0c55ad015a3bf4f1b2b0b822cd15d6c15b0f00a08
X-Wajub-Timestamp: 1748083260
X-Wajub-Event: payment.succeeded
Content-Type: application/json
{"id":"evt_…","event":"payment.succeeded","data":{…}}Answer first, work after
A delivery that takes longer than 10 seconds counts as failed and is retried, so a handler that fulfils the order before responding is a handler that fulfils it five times. Acknowledge, then queue.
Point the CLI at it
npm install -g @wajub/cli
wajub login
wajub listen --forward-to localhost:3000/webhooks/wajubThe CLI prints a signing secret when it starts. Put that one in WAJUB_WEBHOOK_SECRET while you
develop: forwarded events are signed with it, so your verification code runs for real rather than
being skipped locally.
| Flag | What it does |
|---|---|
--forward-to | Where to POST. http:// is assumed when you leave it off |
--events | Comma-separated types, instead of everything |
--secret | Use a secret you already have, instead of a generated one |
Make something happen
wajub trigger drives the sandbox API end to end and lets the real event come back to you. It is
not a synthetic payload: a payment is actually created and processed.
wajub trigger --list
wajub trigger payment.succeeded
wajub trigger payment.failed --amount 2500
wajub trigger customer.created -d name="Acme Inc"Eight events can be triggered this way, and wajub trigger --list prints them.
| Resource | Events |
|---|---|
| Payments | payment.created, payment.succeeded, payment.failed, payment.cancelled |
| Refunds | refund.succeeded, refund.failed |
| Customers | customer.created, customer.updated |
Anything outside that list you produce the normal way: create the resource with a sandbox test number and let it run.
Register the endpoint for real
The CLI covers development. Production needs a registered HTTPS endpoint, created in Konsole or through the API.
curl https://api.wajub.com/webhooks \
-H "Authorization: sk.kZ3qP8mWvL2xR7tB5nY4hC6dF9jS1aG0eU3i…" \
-H "Content-Type: application/json" \
-d '{
"url": "https://example.com/webhooks/wajub",
"events": ["payment.succeeded", "payment.failed", "refund.succeeded"]
}'The response carries a whsec_ secret, and that is the only time it is returned in full. Store it
before you move on. Subscribe to the events you handle rather than to everything: an endpoint
receiving types it ignores still has to answer them, and every one of them counts against your
timeout budget.
Check the delivery, not just your logs
Konsole keeps every attempt with its response code and body. If your handler never ran, the
answer is in Webhooks: a 404 means the URL is wrong,
a timeout means you answered too late, and no row at all means the event never matched your
subscription.
Webhook deliveries
| Method | Endpoint | Status | Duration | Attempt |
|---|---|---|---|---|
| POST | /webhooks/wajub | 200 | 86 ms | 1 of 5 |