Webhooks

Webhooks allow you to receive real-time notifications when events occur in your Postscale account. Instead of polling the API, Postscale pushes events to your server as they happen.

Supported Events

Email Events (Postscale Send)

EventDescription
email.sentEmail was accepted for delivery
email.deliveredEmail was successfully delivered
email.bouncedEmail bounced (hard or soft)
email.complainedRecipient marked as spam
email.openedRecipient opened the email
email.clickedRecipient clicked a link
email.unsubscribedRecipient unsubscribed

Inbound Events (Postscale Receive)

EventDescription
inbound.receivedNew email received and parsed
inbound.spamEmail identified as spam
inbound.failedEmail parsing failed

Alias Events (Postscale Shield)

EventDescription
alias.createdNew alias was created
alias.disabledAlias was disabled
alias.deletedAlias was deleted
alias.forwardedEmail was forwarded via alias

Setting Up Webhooks

Via Dashboard

  1. Go to SettingsWebhooks in the dashboard
  2. Click Add Endpoint
  3. Enter your endpoint URL (must be HTTPS)
  4. Select the events you want to receive
  5. Click Create Endpoint

Via API

curl -X POST https://api.postscale.io/v1/webhooks \
  -H "Authorization: Bearer ps_live_your_api_key" \
  -H "Content-Type: application/json" \
  -d '{
    "url": "https://yourapp.com/webhooks/postscale",
    "events": ["email.delivered", "email.bounced", "inbound.received"]
  }'

Webhook Payload

All webhook payloads follow this structure:

{
  "id": "evt_abc123",
  "type": "email.delivered",
  "created_at": "2026-01-18T10:30:00Z",
  "data": {
    // Event-specific data
  }
}

Example: email.delivered

{
  "id": "evt_abc123",
  "type": "email.delivered",
  "created_at": "2026-01-18T10:30:00Z",
  "data": {
    "email_id": "msg_xyz789",
    "from": "hello@yourapp.com",
    "to": "user@example.com",
    "subject": "Welcome!",
    "delivered_at": "2026-01-18T10:30:00Z",
    "mx_host": "gmail-smtp-in.l.google.com"
  }
}

Example: inbound.received

{
  "id": "evt_def456",
  "type": "inbound.received",
  "created_at": "2026-01-18T10:30:00Z",
  "data": {
    "email_id": "in_abc123",
    "from": "customer@example.com",
    "to": "support@yourapp.com",
    "subject": "Help with my order",
    "text": "Hi, I need help...",
    "html": "<p>Hi, I need help...</p>",
    "attachments": [
      {
        "filename": "screenshot.png",
        "content_type": "image/png",
        "size": 45678,
        "url": "https://files.postscale.io/..."
      }
    ],
    "headers": {
      "message-id": "<unique-id@example.com>",
      "in-reply-to": null
    }
  }
}

Verifying Webhooks

Every webhook request includes a signature in the X-Postscale-Signature header. Verify this signature to ensure the request came from Postscale.

Signature Format

X-Postscale-Signature: t=1705577400,v1=5d41402abc4b2a76b9719d911017c592
  • t — Timestamp when the signature was generated
  • v1 — HMAC-SHA256 signature

Verification Algorithm

  1. Extract the timestamp and signature from the header
  2. Construct the signed payload: {timestamp}.{request_body}
  3. Compute HMAC-SHA256 using your webhook secret
  4. Compare the computed signature with the received signature

Code Examples

Node.js:

import crypto from 'crypto';

function verifyWebhook(payload, signature, secret) {
  const [tPart, v1Part] = signature.split(',');
  const timestamp = tPart.split('=')[1];
  const receivedSig = v1Part.split('=')[1];

  const signedPayload = `${timestamp}.${payload}`;
  const expectedSig = crypto
    .createHmac('sha256', secret)
    .update(signedPayload)
    .digest('hex');

  return crypto.timingSafeEqual(
    Buffer.from(receivedSig),
    Buffer.from(expectedSig)
  );
}

Python:

import hmac
import hashlib

def verify_webhook(payload: str, signature: str, secret: str) -> bool:
    parts = dict(p.split('=') for p in signature.split(','))
    timestamp = parts['t']
    received_sig = parts['v1']

    signed_payload = f"{timestamp}.{payload}"
    expected_sig = hmac.new(
        secret.encode(),
        signed_payload.encode(),
        hashlib.sha256
    ).hexdigest()

    return hmac.compare_digest(received_sig, expected_sig)

Retry Policy

If your endpoint returns an error (non-2xx status), Postscale retries with exponential backoff:

AttemptDelay
1Immediate
21 minute
35 minutes
430 minutes
52 hours
68 hours
724 hours

After 7 failed attempts, the webhook is marked as failed and no further retries are attempted.

Best Practices

Respond quickly

Return a 2xx response as quickly as possible. Process the webhook asynchronously if needed.

  1. Return 200 immediately: Acknowledge receipt before processing
  2. Use a queue: Push webhooks to a queue for async processing
  3. Handle duplicates: Webhooks may be sent multiple times; use the event ID for deduplication
  4. Verify signatures: Always verify the webhook signature
  5. Log everything: Keep logs for debugging and auditing

Testing Webhooks

Test Events

Send test events from the dashboard:

  1. Go to SettingsWebhooks
  2. Click on your endpoint
  3. Click Send Test Event
  4. Select an event type and click Send

Local Development

For local development, use a tunnel service:

# Using ngrok
ngrok http 3000

# Use the generated URL as your webhook endpoint
# https://abc123.ngrok.io/webhooks/postscale
Webhook logs

View recent webhook deliveries and responses in the dashboard under WebhooksLogs.