Webhooks
Webhooks let you receive a POST request the moment an email hits an inbox — no polling required.
Base URL: https://api.inboxical.com/v1
Create a webhook endpoint
Section titled “Create a webhook endpoint”POST /webhooksRegisters a new webhook endpoint. A signing secret is generated automatically (or you can provide your own).
Request body
| Field | Type | Required | Description |
|---|---|---|---|
url | string | Yes | The URL to receive POST requests |
events | string[] | No | Events to subscribe to (default: ["message.received"]) |
secret | string | No | HMAC signing secret. Auto-generated if omitted |
Supported events: message.received
Example
curl -X POST https://api.inboxical.com/v1/webhooks \ -H "X-API-Key: $API_KEY" \ -H "Content-Type: application/json" \ -d '{ "url": "https://yourapp.com/hooks/email", "events": ["message.received"] }'Response 201
{ "id": "wh_abc123", "url": "https://yourapp.com/hooks/email", "events": ["message.received"], "is_active": true, "created_at": "2026-03-21T12:00:00Z", "secret": "a1b2c3d4e5f6..."}List webhook endpoints
Section titled “List webhook endpoints”GET /webhooksReturns all webhook endpoints for the authenticated user.
curl https://api.inboxical.com/v1/webhooks \ -H "X-API-Key: $API_KEY"Response 200
{ "endpoints": [ { "id": "wh_abc123", "url": "https://yourapp.com/hooks/email", "events": ["message.received"], "is_active": true, "created_at": "2026-03-21T12:00:00Z" } ]}Get a webhook endpoint
Section titled “Get a webhook endpoint”GET /webhooks/:idReturns a single endpoint along with its 20 most recent delivery attempts.
curl https://api.inboxical.com/v1/webhooks/wh_abc123 \ -H "X-API-Key: $API_KEY"Response 200
{ "id": "wh_abc123", "url": "https://yourapp.com/hooks/email", "events": ["message.received"], "is_active": true, "created_at": "2026-03-21T12:00:00Z", "recent_deliveries": [ { "id": "del_xyz", "event": "message.received", "status": "delivered", "attempts": 1, "response_status": 200, "created_at": "2026-03-21T12:01:00Z" } ]}Update a webhook endpoint
Section titled “Update a webhook endpoint”PUT /webhooks/:idUpdate the URL, events, active status, or secret.
Request body (all fields optional)
| Field | Type | Description |
|---|---|---|
url | string | New URL |
events | string[] | New event subscriptions |
is_active | boolean | Enable or disable the endpoint |
secret | string | New signing secret |
curl -X PUT https://api.inboxical.com/v1/webhooks/wh_abc123 \ -H "X-API-Key: $API_KEY" \ -H "Content-Type: application/json" \ -d '{ "is_active": false }'Delete a webhook endpoint
Section titled “Delete a webhook endpoint”DELETE /webhooks/:idPermanently removes the endpoint and all its delivery history.
curl -X DELETE https://api.inboxical.com/v1/webhooks/wh_abc123 \ -H "X-API-Key: $API_KEY"Response 200
{ "message": "Webhook endpoint deleted"}Delivery logs
Section titled “Delivery logs”GET /webhooks/:id/deliveriesPaginated list of all delivery attempts for an endpoint.
Query parameters
| Param | Default | Description |
|---|---|---|
page | 1 | Page number |
limit | 20 | Items per page (max 100) |
curl "https://api.inboxical.com/v1/webhooks/wh_abc123/deliveries?page=1&limit=10" \ -H "X-API-Key: $API_KEY"Response 200
{ "deliveries": [ { "id": "del_xyz", "webhook_endpoint_id": "wh_abc123", "event": "message.received", "payload": { "inbox_id": "ix_3f9a", "message_id": "msg_7b2c", "..." : "..." }, "status": "delivered", "attempts": 1, "last_attempt_at": "2026-03-21T12:01:00Z", "response_status": 200, "response_body": "OK", "created_at": "2026-03-21T12:01:00Z" } ], "pagination": { "page": 1, "limit": 10, "total": 42, "pages": 5 }}Delivery statuses
Section titled “Delivery statuses”| Status | Meaning |
|---|---|
pending | Delivery queued, not yet attempted |
delivered | Your server responded with a 2xx status |
failed | Non-2xx response or network error (will be retried) |
dead | Failed after 3 attempts — no further retries |
Test a webhook
Section titled “Test a webhook”POST /webhooks/:id/testSends a test delivery to the endpoint with a sample payload. The delivery is recorded in the delivery log and the result is returned synchronously.
curl -X POST https://api.inboxical.com/v1/webhooks/wh_abc123/test \ -H "X-API-Key: $API_KEY"Response 200
{ "delivery": { "id": "del_test123", "event": "message.received", "status": "delivered", "attempts": 1, "response_status": 200, "response_body": "OK", "created_at": "2026-03-21T12:05:00Z" }}The test payload looks like:
{ "test": true, "inbox_id": "test-inbox-id", "message_id": "test-message-id", "subject": "Test webhook delivery", "timestamp": "2026-03-21T12:05:00Z"}Webhook payload
Section titled “Webhook payload”When a real message arrives, Inboxical sends a POST to your URL with:
{ "event": "message.received", "inbox_id": "ix_3f9a", "message": { "id": "msg_7b2c", "subject": "Welcome to MyApp!", "received_at": "2026-03-21T12:00:03Z" }}Respond with any 2xx status to acknowledge.
Retries
Section titled “Retries”Inboxical retries failed deliveries up to 3 times with an exponential backoff of attempts * 30 seconds. After 3 failures, the delivery is marked as dead and no further retries are attempted.
HMAC signature verification
Section titled “HMAC signature verification”Every webhook delivery includes an X-Inboxical-Signature header containing an HMAC-SHA256 signature of the raw request body, signed with your endpoint’s secret.
Headers sent with each delivery
Section titled “Headers sent with each delivery”| Header | Description |
|---|---|
X-Inboxical-Event | Event type (e.g. message.received) |
X-Inboxical-Delivery-Id | Unique delivery ID |
X-Inboxical-Signature | HMAC-SHA256 hex digest of the request body |
Content-Type | application/json |
Verifying the signature
Section titled “Verifying the signature”import crypto from 'node:crypto'
function verifyWebhook(body: string, signature: string, secret: string): boolean { const expected = crypto .createHmac('sha256', secret) .update(body) .digest('hex') return crypto.timingSafeEqual( Buffer.from(signature, 'hex'), Buffer.from(expected, 'hex'), )}
// Express exampleapp.post('/hooks/email', (req, res) => { const rawBody = req.body // must be the raw string, not parsed JSON const signature = req.headers['x-inboxical-signature'] as string
if (!verifyWebhook(rawBody, signature, process.env.WEBHOOK_SECRET!)) { return res.status(401).send('Invalid signature') }
const payload = JSON.parse(rawBody) console.log('New email:', payload.message.subject) res.sendStatus(200)})Using webhooks in tests
Section titled “Using webhooks in tests”Webhooks work well when your test environment is reachable from the internet (e.g. staging). For local development, prefer long-polling with ?wait=30.
// Staging — register webhook before testconst res = await fetch('https://api.inboxical.com/v1/webhooks', { method: 'POST', headers: { 'X-API-Key': API_KEY, 'Content-Type': 'application/json' }, body: JSON.stringify({ url: 'https://staging.myapp.com/hooks/test' }),})const { secret } = await res.json()// Store secret for signature verification