Testing locally
How to receive Kwill webhook events on your local development machine.
Kwill can only deliver events to publicly reachable HTTPS URLs. To test webhooks locally, you need a tunnel that forwards traffic from a public URL to your local server.
Step 1 — Start your local server
Start your webhook handler on a local port. For example, with Express on port 3000:
node server.js
# Listening on http://localhost:3000Step 2 — Open a tunnel
ngrok (recommended)
ngrok is the most widely used option and has a free tier that works for local testing.
# Install ngrok (first time only)
brew install ngrok # macOS
# or download from https://ngrok.com/download
# Start the tunnel
ngrok http 3000ngrok prints a public URL like https://a1b2c3d4.ngrok-free.app. Copy it.
Alternatives
| Tool | Install | Command |
|---|---|---|
| Cloudflare Tunnel | brew install cloudflared | cloudflared tunnel --url http://localhost:3000 |
| localtunnel | npm i -g localtunnel | lt --port 3000 |
Step 3 — Register a webhook endpoint
Use the public URL from your tunnel as the webhook URL. Append your handler path:
curl -X POST https://usekwill.com/api/v1/webhooks \
-H "Authorization: Bearer $KWILL_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"url": "https://a1b2c3d4.ngrok-free.app/webhooks/kwill",
"event_types": ["invoice.paid", "proposal.signed"]
}'The response includes your id and the secret (shown once — save it as KWILL_WEBHOOK_SECRET).
Step 4 — Send a test event
From the dashboard: open the webhook endpoint, click Send test event.
Or via the API:
curl -X POST https://usekwill.com/api/v1/webhooks/{id}/test \
-H "Authorization: Bearer $KWILL_API_KEY"Check your local server logs — you should see the webhook.test payload arrive. Verify that your signature check passes.
Step 5 — Trigger real events
Perform an action in Kwill that fires one of your subscribed events. For example, pay a test invoice in the dashboard to trigger invoice.paid.
Watch the ngrok web UI at http://localhost:4040 to inspect the full request and response in real time.
Cleanup
Delete the local webhook endpoint when you're done:
curl -X DELETE https://usekwill.com/api/v1/webhooks/{id} \
-H "Authorization: Bearer $KWILL_API_KEY"Webhook deletions are allowed even on frozen workspaces.