Webhooks
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)
| Event | Description |
|---|---|
email.sent | Email was accepted for delivery |
email.delivered | Email was successfully delivered |
email.bounced | Email bounced (hard or soft) |
email.complained | Recipient marked as spam |
email.opened | Recipient opened the email |
email.clicked | Recipient clicked a link |
email.unsubscribed | Recipient unsubscribed |
Inbound Events (Postscale Receive)
| Event | Description |
|---|---|
inbound.received | New email received and parsed |
inbound.spam | Email identified as spam |
inbound.failed | Email parsing failed |
Alias Events (Postscale Shield)
| Event | Description |
|---|---|
alias.created | New alias was created |
alias.disabled | Alias was disabled |
alias.deleted | Alias was deleted |
alias.forwarded | Email was forwarded via alias |
Setting Up Webhooks
Via Dashboard
- Go to Settings → Webhooks in the dashboard
- Click Add Endpoint
- Enter your endpoint URL (must be HTTPS)
- Select the events you want to receive
- 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 generatedv1— HMAC-SHA256 signature
Verification Algorithm
- Extract the timestamp and signature from the header
- Construct the signed payload:
{timestamp}.{request_body} - Compute HMAC-SHA256 using your webhook secret
- 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:
| Attempt | Delay |
|---|---|
| 1 | Immediate |
| 2 | 1 minute |
| 3 | 5 minutes |
| 4 | 30 minutes |
| 5 | 2 hours |
| 6 | 8 hours |
| 7 | 24 hours |
After 7 failed attempts, the webhook is marked as failed and no further retries are attempted.
Best Practices
Return a 2xx response as quickly as possible. Process the webhook asynchronously if needed.
- Return 200 immediately: Acknowledge receipt before processing
- Use a queue: Push webhooks to a queue for async processing
- Handle duplicates: Webhooks may be sent multiple times; use the event ID for deduplication
- Verify signatures: Always verify the webhook signature
- Log everything: Keep logs for debugging and auditing
Testing Webhooks
Test Events
Send test events from the dashboard:
- Go to Settings → Webhooks
- Click on your endpoint
- Click Send Test Event
- 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
View recent webhook deliveries and responses in the dashboard under Webhooks → Logs.