Webhooks
PushMail receives email events from your sending provider and stores them as structured event records. You can also configure webhooks to notify your own application when events occur.
Event types
| Event | Description | Action |
|---|---|---|
delivered | Email accepted by recipient server | Send status → delivered |
open | Recipient opened the email | Campaign opened count +1 |
click | Recipient clicked a link | URL stored in event data |
bounce | Email bounced (hard or soft) | Contact suppressed |
spam_report | Recipient marked as spam | Contact suppressed |
unsubscribe | Recipient unsubscribed via link | Contact status → unsubscribed |
dropped | Email dropped by provider (suppressed, invalid, etc.) | Send status → failed |
deferred | Delivery temporarily delayed by recipient server | Logged for monitoring |
Query events
GET /v1/events?siteId=:siteId
curl "https://pushmail.dev/api/v1/events?siteId=1&type=bounce&page=1&limit=50" \
-H "Authorization: Bearer pm_live_YOUR_KEY"{
"data": {
"events": [
{
"id": 1,
"sendId": 123,
"toEmail": "user@example.com",
"subject": "Welcome to our platform",
"type": "bounce",
"data": {
"reason": "550 5.1.1 The email account does not exist",
"bounceType": "hard"
},
"ipAddress": "192.168.1.1",
"timestamp": "2025-01-15T10:05:00.000Z"
}
],
"pagination": { "page": 1, "limit": 50, "total": 1, "totalPages": 1 }
}
}Event data by type
The data field contains type-specific information:
{
"type": "click",
"data": {
"url": "https://yourdomain.com/pricing"
}
}{
"type": "bounce",
"data": {
"reason": "550 5.1.1 The email account does not exist",
"bounceType": "hard"
}
}{
"type": "open",
"data": {
"userAgent": "Mozilla/5.0..."
}
}Provider webhooks (inbound)
When using BYOK mode, configure your email provider to send event webhooks to PushMail. Each of the 10 supported providers has a dedicated inbound endpoint that normalizes events into the unified format shown above.
Webhook endpoints
Every provider endpoint follows the pattern:
https://pushmail.dev/api/v1/webhooks/{provider}?config_id={sendingConfigId}The config_id query parameter links incoming events to the correct sending config in your org.
| Provider | Endpoint | Verification |
|---|---|---|
| SendGrid | /v1/webhooks/sendgrid?config_id=X | Native ECDSA signature |
| Amazon SES | /v1/webhooks/ses?config_id=X&secret=Y | Query-parameter secret |
| Postmark | /v1/webhooks/postmark?config_id=X&secret=Y | Query-parameter secret |
| Mailgun | /v1/webhooks/mailgun?config_id=X | Native HMAC-SHA256 signature |
| SparkPost | /v1/webhooks/sparkpost?config_id=X | Native token header |
| Brevo | /v1/webhooks/brevo?config_id=X&secret=Y | Query-parameter secret |
| Mailjet | /v1/webhooks/mailjet?config_id=X&secret=Y | Query-parameter secret |
| Mandrill | /v1/webhooks/mandrill?config_id=X | Native HMAC-SHA1 signature |
| Resend | /v1/webhooks/resend?config_id=X | Native Svix HMAC-SHA256 signature |
| Elastic Email | /v1/webhooks/elastic-email?config_id=X&secret=Y | Query-parameter secret |
Verification strategies
PushMail supports two verification strategies depending on what each provider offers:
Native signature verification -- The provider signs each webhook request with a cryptographic signature in the headers. PushMail verifies the signature using the webhook secret stored on your sending config. Providers: SendGrid, Mailgun, SparkPost, Mandrill, Resend.
https://pushmail.dev/api/v1/webhooks/sendgrid?config_id=123Query-parameter secret -- For providers that do not include a signature in their webhook payloads, append your sending config's webhookSecret as a secret query parameter. PushMail compares it against the stored value before processing. Providers: SES, Postmark, Brevo, Mailjet, Elastic Email.
https://pushmail.dev/api/v1/webhooks/postmark?config_id=123&secret=your_webhook_secretSet the webhookSecret on your sending config when you create it, or update it later via PUT /v1/sending-configs/:id.
See Providers for per-provider setup instructions including where to configure webhooks in each provider's dashboard.
SendGrid setup (example)
- Go to SendGrid Dashboard -> Settings -> Mail Settings
- Enable Event Webhook
- Set the HTTP POST URL to
https://pushmail.dev/api/v1/webhooks/sendgrid?config_id=YOUR_CONFIG_ID - Select events: Delivered, Opened, Clicked, Bounced, Spam Reports, Unsubscribes
- Save
Outgoing webhooks
Forward email events to your own endpoints in real time. PushMail signs every payload with HMAC-SHA256 so you can verify authenticity.
Create a webhook endpoint
POST /v1/webhooks
curl -X POST https://pushmail.dev/api/v1/webhooks \
-H "Authorization: Bearer pm_live_YOUR_KEY" \
-H "Content-Type: application/json" \
-d '{
"url": "https://your-app.com/webhooks/pushmail",
"events": ["delivered", "bounce", "open", "click", "spam_report", "unsubscribe"],
"description": "Production webhook"
}'{
"data": {
"id": 1,
"url": "https://your-app.com/webhooks/pushmail",
"events": ["delivered", "bounce", "open", "click", "spam_report", "unsubscribe"],
"active": true,
"description": "Production webhook",
"secret": "abc123...hex64",
"createdAt": "2026-01-15T10:00:00.000Z"
}
}The secret is only returned when you create the webhook. Store it securely — you'll need it to verify signatures.
Manage webhooks
curl https://pushmail.dev/api/v1/webhooks \
-H "Authorization: Bearer pm_live_YOUR_KEY"curl -X PUT https://pushmail.dev/api/v1/webhooks/1 \
-H "Authorization: Bearer pm_live_YOUR_KEY" \
-H "Content-Type: application/json" \
-d '{
"events": ["delivered", "bounce"],
"active": false
}'curl -X DELETE https://pushmail.dev/api/v1/webhooks/1 \
-H "Authorization: Bearer pm_live_YOUR_KEY"Verify signatures
Every outgoing webhook request includes an X-Webhook-Signature header containing an HMAC-SHA256 hex digest of the request body, signed with your webhook secret.
import { createHmac } from "crypto";
function verifyWebhook(body: string, signature: string, secret: string): boolean {
const expected = createHmac("sha256", secret).update(body).digest("hex");
return signature === expected;
}Payload format
{
"event": "delivered",
"sendId": 123,
"contactId": 42,
"email": "jane@example.com",
"timestamp": "2026-01-15T10:05:00.000Z"
}Next steps
- SDKs & Libraries — Language-specific clients
- API Reference — Complete endpoint documentation