Webhooks
Get real-time notifications when emails arrive instead of polling for new messages.
Why Webhooks?
Webhooks push notifications to your server instantly when events occur:
Without Webhooks (Polling):
// Poll every 30 seconds - slow and inefficient
setInterval(async () => {
const messages = await client.inboxes.messages(inboxId).list({
direction: 'in',
limit: 10
})
// Process new messages...
}, 30000)With Webhooks (Real-time):
// Get notified instantly when emails arrive
app.post('/webhook', (req, res) => {
const event = req.body
if (event.type === 'message.received') {
// Process message immediately!
}
res.sendStatus(200)
})Create a Webhook
const webhook = await client.webhooks.create({
url: 'https://your-server.com/webhook',
events: ['message.received'],
inbox_id: 'inbox_abc123' // Optional: specific inbox
})
console.log(webhook.id)
// → webhook_xyz789
console.log(webhook.secret)
// → whsec_abc123def456 (save this!)Save the Secret
The webhook secret is only shown once during creation. Save it securely - you'll need it to verify webhook signatures.
Parameters
| Parameter | Type | Required | Description |
|---|---|---|---|
url | string | Yes | Your webhook endpoint URL (must be HTTPS) |
events | string[] | Yes | Events to subscribe to |
inbox_id | string | No | Receive events for specific inbox only |
description | string | No | Human-readable description |
Available Events
| Event | Description |
|---|---|
message.received | New email received in an inbox |
message.sent | Email successfully sent |
message.failed | Email delivery failed |
message.bounced | Email bounced |
Subscribe to All Inboxes
Receive events for all your inboxes:
const webhook = await client.webhooks.create({
url: 'https://your-server.com/webhook',
events: ['message.received', 'message.failed'],
description: 'Main webhook for all inboxes'
})Subscribe to Specific Inbox
Receive events for a single inbox:
const webhook = await client.webhooks.create({
url: 'https://support-handler.com/webhook',
events: ['message.received'],
inbox_id: 'inbox_support123',
description: 'Support inbox webhook'
})List Webhooks
const response = await client.webhooks.list({
limit: 20,
offset: 0
})
for (const webhook of response.data) {
console.log(`${webhook.id}: ${webhook.url}`)
console.log(`Events: ${webhook.events.join(', ')}`)
}Get a Webhook
const webhook = await client.webhooks.get('webhook_xyz789')
console.log(webhook.url)
console.log(webhook.events)
console.log(webhook.inbox_id)Update a Webhook
Change the URL, events, or description:
const updated = await client.webhooks.update('webhook_xyz789', {
url: 'https://new-server.com/webhook',
events: ['message.received', 'message.sent'],
description: 'Updated webhook endpoint'
})Can't Rotate Secret
You cannot change the webhook secret after creation. If you need a new secret, delete the webhook and create a new one.
Delete a Webhook
await client.webhooks.delete('webhook_xyz789')Webhook Payload
When an event occurs, Myxara sends a POST request to your webhook URL:
{
"id": "evt_abc123",
"type": "message.received",
"created_at": "2025-01-15T10:30:00Z",
"data": {
"message": {
"id": "msg_xyz789",
"inbox_id": "inbox_abc123",
"from": "[email protected]",
"to": "[email protected]",
"subject": "Need help",
"text": "I need assistance with...",
"html": "<p>I need assistance with...</p>",
"direction": "in",
"created_at": "2025-01-15T10:30:00Z"
}
}
}Handling Webhooks
Express.js Example
import express from 'express'
import { MyxaraClient } from '@myxara/sdk-js'
const app = express()
app.use(express.json())
const client = new MyxaraClient({
apiKey: process.env.MYXARA_API_KEY!
})
app.post('/webhook', async (req, res) => {
const event = req.body
// Verify signature (see below)
const signature = req.headers['myxara-signature'] as string
const isValid = verifySignature(signature, req.body, WEBHOOK_SECRET)
if (!isValid) {
return res.status(401).send('Invalid signature')
}
// Handle event
if (event.type === 'message.received') {
const message = event.data.message
console.log(`New email from ${message.from}`)
console.log(`Subject: ${message.subject}`)
// Auto-reply
await client.inboxes.messages(message.inbox_id).send({
to: message.from,
subject: `Re: ${message.subject}`,
text: 'Thanks for your message. We will respond shortly.',
in_reply_to: message.id
})
}
// Always return 200
res.sendStatus(200)
})
app.listen(3000)Next.js API Route
// app/api/webhook/route.ts
import { NextRequest, NextResponse } from 'next/server'
import { MyxaraClient } from '@myxara/sdk-js'
const client = new MyxaraClient({
apiKey: process.env.MYXARA_API_KEY!
})
export async function POST(req: NextRequest) {
const event = await req.json()
// Verify signature
const signature = req.headers.get('myxara-signature')
// ... verification logic
// Handle event
if (event.type === 'message.received') {
const { message } = event.data
// Process the message...
await processMessage(message)
}
return NextResponse.json({ received: true })
}
async function processMessage(message: any) {
// Your logic here
console.log(`Processing message: ${message.id}`)
}Verifying Signatures
Always verify webhook signatures to ensure requests are from Myxara:
import crypto from 'crypto'
function verifySignature(
signature: string,
payload: any,
secret: string
): boolean {
const expectedSignature = crypto
.createHmac('sha256', secret)
.update(JSON.stringify(payload))
.digest('hex')
return crypto.timingSafeEqual(
Buffer.from(signature),
Buffer.from(expectedSignature)
)
}
// Usage
const signature = req.headers['myxara-signature'] as string
const isValid = verifySignature(signature, req.body, WEBHOOK_SECRET)
if (!isValid) {
return res.status(401).send('Invalid signature')
}Always Verify Signatures
Never skip signature verification in production. Without it, anyone can send fake webhook events to your server.
Retry Behavior
If your webhook endpoint fails (5xx error or timeout), Myxara automatically retries:
- 1st retry: 1 minute later
- 2nd retry: 5 minutes later
- 3rd retry: 30 minutes later
- 4th retry: 2 hours later
- 5th retry: 12 hours later
After 5 failed attempts, the webhook is disabled and you'll receive an email notification.
Responding to Webhooks
Always respond with 200 OK within 5 seconds, even if you haven't finished processing. Queue long-running tasks in the background.
Best Practice: Background Processing
import { Queue } from 'bull'
const messageQueue = new Queue('messages')
app.post('/webhook', async (req, res) => {
const event = req.body
// Verify signature
const isValid = verifySignature(...)
if (!isValid) {
return res.status(401).send('Invalid signature')
}
// Queue for processing (fast response)
if (event.type === 'message.received') {
await messageQueue.add(event.data.message)
}
// Respond immediately
res.sendStatus(200)
})
// Process in background
messageQueue.process(async (job) => {
const message = job.data
// Long-running processing here...
})Testing Webhooks
Local Development with ngrok
# Install ngrok
npm install -g ngrok
# Start your local server
npm run dev
# Expose to internet
ngrok http 3000Use the ngrok URL for your webhook:
const webhook = await client.webhooks.create({
url: 'https://abc123.ngrok.io/webhook',
events: ['message.received']
})Test Event Delivery
Send a test email to trigger the webhook:
await client.inboxes.messages(inboxId).send({
to: '[email protected]',
subject: 'Test webhook',
text: 'This should trigger a webhook event'
})Complete Example
import express from 'express'
import crypto from 'crypto'
import { MyxaraClient } from '@myxara/sdk-js'
const app = express()
app.use(express.json())
const client = new MyxaraClient({
apiKey: process.env.MYXARA_API_KEY!
})
const WEBHOOK_SECRET = process.env.WEBHOOK_SECRET!
// Verify webhook signature
function verifySignature(signature: string, payload: any): boolean {
const expected = crypto
.createHmac('sha256', WEBHOOK_SECRET)
.update(JSON.stringify(payload))
.digest('hex')
return crypto.timingSafeEqual(
Buffer.from(signature),
Buffer.from(expected)
)
}
// Webhook endpoint
app.post('/webhook', async (req, res) => {
// Verify signature
const signature = req.headers['myxara-signature'] as string
if (!verifySignature(signature, req.body)) {
console.error('Invalid webhook signature')
return res.status(401).send('Invalid signature')
}
const event = req.body
console.log(`Received event: ${event.type}`)
// Handle different event types
switch (event.type) {
case 'message.received':
await handleMessageReceived(event.data.message)
break
case 'message.sent':
console.log(`Message ${event.data.message.id} sent`)
break
case 'message.failed':
console.error(`Message ${event.data.message.id} failed`)
break
}
res.sendStatus(200)
})
async function handleMessageReceived(message: any) {
console.log(`New message from ${message.from}`)
console.log(`Subject: ${message.subject}`)
// Auto-reply
await client.inboxes.messages(message.inbox_id).send({
to: message.from,
subject: `Re: ${message.subject}`,
text: 'Thanks for your email. Our AI agent will respond shortly.',
in_reply_to: message.id
})
}
// Create webhook on startup
async function setupWebhook() {
const webhook = await client.webhooks.create({
url: 'https://your-server.com/webhook',
events: ['message.received', 'message.failed'],
description: 'Production webhook'
})
console.log('Webhook created:', webhook.id)
console.log('Secret:', webhook.secret)
console.log('Save this secret to your environment variables!')
}
app.listen(3000, () => {
console.log('Webhook server running on port 3000')
})
// Uncomment to create webhook:
// setupWebhook().catch(console.error)TypeScript Types
import type {
Webhook,
CreateWebhookParams,
UpdateWebhookParams,
WebhookEvent
} from '@myxara/sdk-js'
const params: CreateWebhookParams = {
url: 'https://your-server.com/webhook',
events: ['message.received'],
inbox_id: 'inbox_abc123'
}
const webhook: Webhook = await client.webhooks.create(params)
// Event handler
function handleEvent(event: WebhookEvent) {
if (event.type === 'message.received') {
const message = event.data.message
// TypeScript knows the shape of message
}
}Next Steps
- Messages → - Learn about message objects
- Examples → - See complete webhook examples
- Error Handling → - Handle webhook errors gracefully