Test Webhooks Locally: Tunnels, Signing & Message Log
How to Test Webhooks Locally (Tunnels + Signed Requests + Message Log)
Testing webhooks locally is essential for debugging integrations without deploying to production. This guide shows how to set up a reliable local testing loop using tunnels, signed requests, and the Message Log to verify webhook deliveries. It includes examples to postman send signed webhook request hmac timestamp for local verification.
Reference implementation: SendPromptly Webhooks reference kit (GitHub Pages)
The reliable local testing loop
Tunnel → webhook endpoint → signature verify → ack 200 → async job
The testing loop involves:
- Exposing your local server via a tunnel.
- Receiving webhook requests at your endpoint.
- Verifying signatures.
- Acknowledging with a 200 response.
- Processing the payload asynchronously.
This ensures your endpoint behaves like production.
Use Message Log as source of truth for attempts/outcomes
The Message Log provides a complete view of delivery attempts, including successes, failures, and retries. Use it to confirm your local setup works.
Learn more about delivery rules and retries.
Common gotcha: Always check the Message Log after triggering a test event, as it shows the full delivery history.
Tunnel options
ngrok (quick), Cloudflare Tunnel (stable), localtunnel (simple)
Choose a tunnel based on your needs:
- ngrok: Quick setup, but URLs change on restart.
- Cloudflare Tunnel: Stable, integrates with Cloudflare for security.
- localtunnel: Simple, no account required.
Suggested diagram: A comparison table of tunnel options (e.g., setup time, stability, cost).
Hook your tunnel into SendPromptly
Create webhook endpoint in your project/environment
Configure your SendPromptly project to send webhooks to your tunnel URL. For example, set the webhook URL to https://your-ngrok-url.ngrok.io/webhooks/sendpromptly.
To trigger a real delivery from the live API use:
POST https://app.sendpromptly.com/api/v1/events(include yourAuthorization: Bearer <token>and anIdempotency-Key).
Trigger a test event via API curl
Use the Getting Started curl to trigger a real event and test your endpoint.
Trigger a real event and confirm delivery runs.
Use Sample Project to generate a real delivery and verify the run in Message Log.
Micro checklist:
- Confirm tunnel URL is configured in SendPromptly webhook settings
- Trigger a test event (Getting Started curl)
- Watch Message Log for the delivery attempt and status
Postman/curl signed requests (when you don’t want to trigger SendPromptly)
Generate signature locally
To test signatures without triggering SendPromptly, generate the signature locally using your secret. This is the same flow used to postman send signed webhook request hmac timestamp when validating locally.
Send to localhost endpoint
Send the signed request to your local endpoint using Postman or curl.
| |
Minimal test snippet:
1 2 3 4 5 6 7 8 9TS=$(date +%s) BODY='{"event_key":"order.created","payload":{"order_id":"O-1001"}}' SIG=$(php -r '$ts=getenv("TS");$b=getenv("BODY");$s=getenv("SECRET");echo hash_hmac("sha256",$ts.".".$b,$s);' \ TS="$TS" BODY="$BODY" SECRET="replace_me") curl -i -X POST "http://localhost:8000/webhooks/sendpromptly" \ -H "Content-Type: application/json" \ -H "X-SP-Timestamp: $TS" \ -H "X-SP-Signature: v1=$SIG" \\ --data "$BODY"
Learn more about signed local requests.
Debug playbook
Missing headers
Check that all required headers (X-SP-Timestamp, X-SP-Signature) are present in the request.
Signature mismatch
Verify you’re using the raw body and correct secret. Compare computed and received signatures.
Timeouts
Ensure your endpoint responds quickly (under 10 seconds) to avoid retries.
Micro checklist:
- Confirm tunnel is running and URL is correct.
- Verify signature generation uses raw body.
- Check for firewall blocks.
- Monitor response times.
Common failure modes
- Tunnel URL changed (ngrok restart) → deliveries fail.
- HTTPS/TLS mismatch → connection errors.
- Signature headers missing → verification rejects (401). ([sendpromptly.com][1])
- Local app too slow → timeouts → retries.
- Middleware signs the wrong bytes (decoded JSON) → mismatch.
- Firewall blocks inbound to local dev.
Learn more about debug workflow using Message Log.
Conclusion
When it fails, open Message Log → inspect status/latency → adjust endpoint until it’s consistently 2xx.
Key takeaways
- Use tunnels to expose localhost for testing.
- Verify signatures using raw body and constant-time comparisons.
- Trigger real events via SendPromptly and check Message Log.
- Debug with signed requests in Postman or curl.
- Monitor for common failures like timeouts and header mismatches.