PushMail.dev

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

EventDescriptionAction
deliveredEmail accepted by recipient serverSend status → delivered
openRecipient opened the emailCampaign opened count +1
clickRecipient clicked a linkURL stored in event data
bounceEmail bounced (hard or soft)Contact suppressed
spam_reportRecipient marked as spamContact suppressed
unsubscribeRecipient unsubscribed via linkContact status → unsubscribed
droppedEmail dropped by provider (suppressed, invalid, etc.)Send status → failed
deferredDelivery temporarily delayed by recipient serverLogged for monitoring

Query events

GET /v1/events?siteId=:siteId

List recent events
curl "https://pushmail.dev/api/v1/events?siteId=1&type=bounce&page=1&limit=50" \
  -H "Authorization: Bearer pm_live_YOUR_KEY"
Response
{
  "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:

click event
{
  "type": "click",
  "data": {
    "url": "https://yourdomain.com/pricing"
  }
}
bounce event
{
  "type": "bounce",
  "data": {
    "reason": "550 5.1.1 The email account does not exist",
    "bounceType": "hard"
  }
}
open event
{
  "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.

ProviderEndpointVerification
SendGrid/v1/webhooks/sendgrid?config_id=XNative ECDSA signature
Amazon SES/v1/webhooks/ses?config_id=X&secret=YQuery-parameter secret
Postmark/v1/webhooks/postmark?config_id=X&secret=YQuery-parameter secret
Mailgun/v1/webhooks/mailgun?config_id=XNative HMAC-SHA256 signature
SparkPost/v1/webhooks/sparkpost?config_id=XNative token header
Brevo/v1/webhooks/brevo?config_id=X&secret=YQuery-parameter secret
Mailjet/v1/webhooks/mailjet?config_id=X&secret=YQuery-parameter secret
Mandrill/v1/webhooks/mandrill?config_id=XNative HMAC-SHA1 signature
Resend/v1/webhooks/resend?config_id=XNative Svix HMAC-SHA256 signature
Elastic Email/v1/webhooks/elastic-email?config_id=X&secret=YQuery-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=123

Query-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_secret

Set 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)

  1. Go to SendGrid Dashboard -> Settings -> Mail Settings
  2. Enable Event Webhook
  3. Set the HTTP POST URL to https://pushmail.dev/api/v1/webhooks/sendgrid?config_id=YOUR_CONFIG_ID
  4. Select events: Delivered, Opened, Clicked, Bounced, Spam Reports, Unsubscribes
  5. 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

Request
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"
  }'
Response
{
  "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

List all webhooks
curl https://pushmail.dev/api/v1/webhooks \
  -H "Authorization: Bearer pm_live_YOUR_KEY"
Update a webhook
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
  }'
Delete a webhook
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.

Verify in your app
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

On this page