API docs
Webhooks
Get told on your own server when the balance runs low, or credits or the own-domain add-on run out.
A webhook is a POST request WeSend sends to your server when something happens. Use it to top up automatically, alert your team, or pause your own sending.
- 1
Add an address
In the dashboard under Settings, Webhook, add an
https://address and choose the events it receives. A project can have up to 5. - 2
Keep the secret
The secret starting with
whsec_is shown once. Keep it in your server's environment. - 3
Test it
The Test button sends a
webhook.testevent and shows your server's answer at once.
Events
{
"id": "evt_9b2f6c1e4a7d4f0e8c3b5a6d7e8f9012",
"type": "balance.low",
"createdAt": "2026-10-20T03:15:30.000Z",
"projectId": "7f3c2a10-5b8e-4d6a-9c1f-2e3d4b5a6c7d",
"data": {
"balance": 4200,
"threshold": 5000
}
}id identifies the event. A retried request carries the same id, so store it to avoid handling one event twice.
Headers
Verify the signature
v1 is the HMAC-SHA256 of t and the raw body joined by a dot (t.body). Check it against the body exactly as received, before parsing it, and refuse a t older than 5 minutes.
import crypto from 'node:crypto'
import express from 'express'
const app = express()
app.post('/wesend/webhook', express.raw({ type: 'application/json' }), (req, res) => {
const header = req.get('WeSend-Signature') || ''
const { t, v1 } = Object.fromEntries(header.split(',').map(part => part.split('=')))
const expected = crypto.createHmac('sha256', process.env.WESEND_WEBHOOK_SECRET)
.update(`${t}.${req.body}`).digest('hex')
const fresh = Math.abs(Date.now() / 1000 - Number(t)) < 300
const valid = v1?.length === expected.length && crypto.timingSafeEqual(Buffer.from(v1), Buffer.from(expected))
if(!fresh || !valid) return res.sendStatus(400)
const event = JSON.parse(req.body)
if(event.type === 'balance.low') {
// Top up, alert the team...
}
res.sendStatus(200)
})Retries
- A
2xxanswer within 10 seconds counts as delivered. Any other answer, a timeout or no connection is retried. - Retries come after 1, 5 and 30 minutes, then 2, 6 and 12 hours, 7 tries in all. After that it is marked failed.
- Redirects are not followed. The address must be a public
httpsserver. - The last 30 days of deliveries and answers are under Settings, Webhook.
The project's members are also told by email, and its owner by SMS. A webhook adds to those; it does not replace them.