Webhooks
Use Motorical delivery webhooks to push real-time email events to your server — deliveries, bounces, complaints, and more.
Managing Webhooks
All webhook endpoints require the webhooks.manage scope.
List Webhooks
curl -H "Authorization: Bearer $TOKEN" \
"$BASE_URL/motor-blocks/$BLOCK_ID/webhooks"
Response: Array of webhook endpoints with id, url, events, enabled, failure_count, last_delivery_at, and created_at.
Create a Webhook
curl -X POST -H "Authorization: Bearer $TOKEN" \
-H "Content-Type: application/json" \
-d '{"url":"https://example.com/webhook","events":["message.delivered","message.bounced"]}' \
"$BASE_URL/motor-blocks/$BLOCK_ID/webhooks"
If events is omitted, the webhook subscribes to all six events listed under Available Events. Pass an explicit events array for a subset — in particular, omit message.processed and message.deferred unless your application handles intermediate states.
The 201 response returns the signing secret once — it is never retrievable again:
{
"success": true,
"data": {
"id": "9c2f0d81-4b6e-4d2a-9f11-3a7e5c0b8d42",
"url": "https://example.com/webhook",
"events": ["message.delivered", "message.bounced"],
"enabled": true,
"created_at": "2026-08-25T12:00:00.000Z",
"secret": "…48 hex chars…",
"secretMasked": "ab12…"
},
"message": "Webhook created. The full \"secret\" is shown once — store it now for signature verification (X-Motorical-Signature)."
}
Store data.secret immediately. It is the key for the HMAC-SHA256 verification below; losing it means deleting and recreating the webhook.
Update a Webhook
curl -X PUT -H "Authorization: Bearer $TOKEN" \
-H "Content-Type: application/json" \
-d '{"url":"https://example.com/new-url","enabled":true}' \
"$BASE_URL/motor-blocks/$BLOCK_ID/webhooks/$WEBHOOK_ID"
Updatable fields: url, events (array), enabled (boolean).
Delete a Webhook
curl -X DELETE -H "Authorization: Bearer $TOKEN" \
"$BASE_URL/motor-blocks/$BLOCK_ID/webhooks/$WEBHOOK_ID"
Test a Webhook
Send a test delivery to verify your endpoint is reachable:
curl -X POST -H "Authorization: Bearer $TOKEN" \
"$BASE_URL/motor-blocks/$BLOCK_ID/webhooks/$WEBHOOK_ID/test"
Returns 202 Accepted with a queued test delivery.
Delivery History
View recent webhook deliveries with status and latency:
curl -H "Authorization: Bearer $TOKEN" \
"$BASE_URL/motor-blocks/$BLOCK_ID/webhooks/$WEBHOOK_ID/deliveries?limit=50"
Parameters:
| Parameter | Description |
|---|---|
limit | Max deliveries to return (1–200, default 50) |
Response: Array of deliveries with eventType, status, attempts, error, latencyMs, createdAt, and deliveredAt.
Signature Verification
Every webhook POST is signed with X-Motorical-Signature — an HMAC-SHA256 of the JSON body using your secret. Always verify this signature before processing the payload.
const crypto = require('crypto');
function verifyWebhookSignature(body, signature, secret) {
const expected = crypto
.createHmac('sha256', secret)
.update(JSON.stringify(body))
.digest('hex');
return crypto.timingSafeEqual(
Buffer.from(signature),
Buffer.from(expected)
);
}
Available Events
| Event | Description |
|---|---|
message.processed | Accepted by the gateway and handed to delivery — an intermediate event, not a final outcome |
message.deferred | Delivery temporarily delayed; a retry is scheduled |
message.delivered | Accepted by the recipient's server |
message.bounced | Bounced (hard or soft) |
message.failed | Permanent delivery failure |
message.complained | Recipient marked the email as spam |
Monitoring Webhook Health
Check delivery statistics for a webhook endpoint:
curl -H "Authorization: Bearer $TOKEN" \
"$BASE_URL/motor-blocks/$BLOCK_ID/webhooks/$WEBHOOK_ID/stats?hours=24"
Response includes: delivered count, failed count, queued count, failure rate, and average latency.
Best Practices
- Respond quickly — return
200 OKwithin 5 seconds - Process asynchronously — queue webhook payloads for background processing
- Verify signatures — always validate
X-Motorical-Signature - Handle retries — Motorical retries failed deliveries with exponential backoff
- Use HTTPS — webhook URLs must use TLS