Webhook failing
Diagnosis for when webhooks don't arrive, return errors, or the HMAC signature doesn't validate.
If your webhooks are failing, this guide covers the most common issues in order of frequency.
1. Check the delivery log
Dashboard → Settings → Webhooks → your endpoint → View deliveries.
The log shows:
- HTTP status returned by your server
- Request body sent
- Your server's response
- Next retry (if pending)
Identifying the HTTP status is the first step toward the right diagnosis.
Diagnosis by HTTP status
200 but no processing
If the delivery shows as successful but your system didn't process the event:
- Verify your handler is actually being called (add a log at the start of the function)
- Confirm the endpoint receives
POST(notGET) - Check if any parsing middleware is transforming the body before the handler runs
400 — Bad Request
Your server is rejecting the payload format.
- Confirm you're receiving
Content-Type: application/json - If using
express.json(), switch toexpress.raw()so you can verify HMAC before parsing:
// Wrong for HMAC webhooks:
app.use(express.json())
// Correct:
app.post('/webhook', express.raw({ type: 'application/json' }), handler)
401 / 403 — Unauthorized
Your server is rejecting the request.
- If you have your own authentication on the endpoint, check that Simple Agent is sending the expected header
- Simple Agent webhooks don't send additional authentication beyond the HMAC signature — use HMAC verification as your authentication
404 — Not Found
The webhook URL is wrong or the endpoint was removed.
- Confirm the URL in the dashboard — it should have no extra trailing slash or incorrect path
- Test with curl:
curl -X POST https://yoursite.com/webhook -H "Content-Type: application/json" -d '{}'
500 — Server error
There's an unhandled error in your handler.
- Add try/catch around all processing logic
- Return
200immediately and process asynchronously if the processing takes time
timeout — No response within 10s
Your handler is taking more than 10 seconds.
Solution: Return 200 immediately and process in the background:
app.post('/webhook', async (req, res) => {
res.status(200).json({ received: true }); // respond immediately
// process in the background (doesn't block the response)
processEvent(req.body).catch(console.error);
});
HMAC signature not validating
Most common issue: body was parsed before verification
The signature is calculated over the raw body (string). If you parse the JSON before verifying, the comparison fails:
// WRONG - body is already an object, not a string
const rawBody = JSON.stringify(req.body); // this is NOT the same as the original body
// CORRECT - use the raw body
app.post('/webhook', express.raw({ type: '*/*' }), (req, res) => {
const rawBody = req.body.toString(); // original string sent by Simple Agent
// verify HMAC with rawBody...
});
Timestamp too old
The default verification rejects events older than 5 minutes. If your server's clock is out of sync:
# Check NTP drift
timedatectl status
ntpq -p
Wrong secret
Confirm you're using the Webhook Secret from the specific webhook's edit screen, not the account API key.
Test with curl
To check whether the problem is in Simple Agent or your server:
# Simulate a basic event without HMAC verification
curl -X POST https://yoursite.com/webhook \
-H "Content-Type: application/json" \
-H "X-Simple Agent-Signature: sha256=test" \
-H "X-Simple Agent-Timestamp: $(date +%s)" \
-d '{"event":"test","id":"evt_test","created_at":"2026-05-14T10:00:00Z","agent_id":"ag_test","data":{}}'
If your server responds 200, the problem is with the HMAC signature or the URL. If it responds with an error, the problem is in your handler.
Resend events manually
To resend a specific event without waiting for the automatic retry:
curl -X POST https://simple-agent.me/api/v1/webhooks/wh_xxx/deliveries/del_xxx/retry \
-H "Authorization: Bearer af_live_xxx"
Or from the dashboard: View deliveries → resend icon next to the failed delivery.