{
  "event": "inbound.email.received",
  "data": {
    "email": {...},
    "organization": {
      "id": "org_123",
      "slug": "my-agent"
    }
  },
  "attempt": 1,
  "timestamp": 1715100000
}
Webhooks let your application react the moment something happens in AI Inbx. Currently we fire a webhook whenever a new inbound email is stored:
  • inbound.email.received
Good to know • Webhooks are delivered at-least-once. Make your handler idempotent.
1

Create an HTTPS endpoint

Your server just needs to accept POST requests with a JSON body. The example below uses Next.js App Router but any framework works – Flask, FastAPI, Express, Cloudflare Workers, …
2

Configure the webhook in your dashboard

  1. Go to Webhooks
  2. Click Add Webhook → enter the public URL of your endpoint
  3. (Optional but recommended) Enable secret signing – we’ll display a random secret once. Copy it immediately and set it as AI_INBX_SECRET in your environment.
    • The secret switch is permanent – you can’t turn it on/off later. Delete & recreate the webhook if you change your mind.
    • Lost or exposed the secret? Use Rotate secret from the webhook’s dropdown to invalidate the old one and get a new value.
3

Verify every request (recommended)

When a secret is enabled we attach two headers:
  • X-AiInbx-Timestamp – integer seconds since Unix epoch
  • X-AiInbx-Signaturesha256= followed by an HMAC-SHA256 signature calculated over timestamp.body
Verify signature (generic Node.js)
import crypto from 'node:crypto'
import type { IncomingMessage } from 'http'

const isValidAiInbxSignature = (
  req: IncomingMessage,
  secret: string,
  rawBody: string
): boolean => {
  const timestamp = req.headers['x-aiinbx-timestamp'] as string | undefined
  const signature = req.headers['x-aiinbx-signature'] as string | undefined
  if (!timestamp || !signature) return false

  const expected =
    'sha256=' +
    crypto.createHmac('sha256', secret).update(`${timestamp}.${rawBody}`).digest('hex')

  return crypto.timingSafeEqual(
    Buffer.from(signature, 'utf8'),
    Buffer.from(expected, 'utf8')
  )
}
4

Manage existing webhooks

Your list of webhooks lives under Webhooks. From here you can:
  • Pause deliveries temporarily and resume them later – useful while doing maintenance on your endpoint.
  • Rotate the secret with a single click if you suspect it has leaked. We immediately stop using the old secret and display the new one once.

Example: Next.js App Router – SDK helper

The easiest way to handle webhooks in Next.js App Router is the createNextRouteHandler helper that ships with the TypeScript SDK. It automatically:
  • Verifies the signature when AI_INBX_SECRET is set
  • Parses the JSON payload
  • Routes the event to the callback you specify
Tip: Your API route can live under any path (e.g., /api/my-inbox-webhook). Just make sure to use that same URL when configuring the webhook in the dashboard.
app/api/aiinbx-webhook/route.ts
import { createNextRouteHandler } from 'aiinbx/helpers'

export const POST = createNextRouteHandler({
  onInboundEmail: async ({ payload, isVerified }) => {
    const { email } = payload.data

    console.log('Received email', { email, isVerified })
    // TODO: your business logic – store email, trigger agent, …
    return Response.json({ received: true })
  },
})
Tip: When no AI_INBX_SECRET is configured the helper still works but isVerified will be undefined.

Example: Next.js App Router – manual verification (advanced)

app/api/ai-inbx-webhook/route.ts
import { NextRequest, NextResponse } from 'next/server'
import crypto from 'node:crypto'

type WebhookPayload = {
  event: 'inbound.email.received'
  data: {
    email: unknown
    organization: {
      id: string
      slug: string
    }
  }
  attempt: number
  timestamp: number
}

const AI_INBX_SECRET = process.env.AI_INBX_SECRET!

const verifySignature = (
  request: NextRequest,
  rawBody: string
): boolean => {
  const timestamp = request.headers.get('x-aiinbx-timestamp') || ''
  const signature = request.headers.get('x-aiinbx-signature') || ''

  const expected =
    'sha256=' +
    crypto.createHmac('sha256', AI_INBX_SECRET).update(`${timestamp}.${rawBody}`).digest('hex')

  return crypto.timingSafeEqual(
    Buffer.from(signature),
    Buffer.from(expected)
  )
}

export async function POST(request: NextRequest) {
  const rawBody = await request.text()

  // ✅ Reject if the signature is invalid
  if (!verifySignature(request, rawBody)) {
    return NextResponse.json({ error: 'Invalid signature' }, { status: 401 })
  }

  const payload = JSON.parse(rawBody) as WebhookPayload
  const {
    data: { email },
    attempt,
    timestamp,
  } = payload

  // TODO: your business logic – store email, trigger agent, …
  console.log('Received email', { email, attempt, timestamp })

  return NextResponse.json({ received: true })
}

Payload structure

FieldTypeDescription
eventstringAlways inbound.email.received for now
data.emailobjectThe full email record (see GET /emails/{id} in the API reference)
data.organizationobjectOrganization context – helpful if you operate multiple orgs
attemptnumberDelivery attempt (starts at 1, increments on retries)
timestampnumberUnix epoch seconds of when we sent the webhook
{
  "event": "inbound.email.received",
  "data": {
    "email": {...},
    "organization": {
      "id": "org_123",
      "slug": "my-agent"
    }
  },
  "attempt": 1,
  "timestamp": 1715100000
}

Failure handling & auto-pause

We attempt to deliver each event once. A non-2xx response or a timeout after 30 seconds counts as a failure. AI Inbx tracks consecutiveFailures for every webhook:
  1. attempt in the payload equals consecutiveFailures + 1 – useful for debugging.
  2. After 3 consecutive failures the webhook is automatically paused (isActive = false).
  3. Paused webhooks stop receiving events until you resume them in the dashboard.
Because we don’t retry the same event internally, make sure your endpoint is highly available or enqueue the payload on your side if you need guaranteed processing.

Building an AI agent?

AI Inbx is the drop-in email stack for agents and apps – send, receive and thread messages with a single API.