Skip to main content

Webhooks

Webhooks let your application react to events as they happen — incoming email, delivery failures, or new agents — without polling the API.

Create a webhook

Register your endpoint in the dashboard under Settings → Webhooks. Provide:
  • URL — A publicly reachable HTTPS endpoint on your server
  • Secret — A string Anima uses to sign each request (generate a random value and store it securely)
  • Events — The event types you want to receive
During development, use a tool like ngrok or Cloudflare Tunnel to expose a local server to the internet.

Event types

EventTriggered when
message.receivedAn agent’s inbox receives a new email
message.sentAn outbound email is successfully delivered
message.failedAn outbound email fails to deliver or bounces
agent.createdA new agent is created in your account

Payload format

Every webhook request is an HTTP POST with a JSON body. The message.received payload looks like this:
{
  "event": "message.received",
  "data": {
    "id": "msg_12345",
    "agent_id": "ag_01abc123",
    "from": "user@example.com",
    "subject": "Hello",
    "body": "Hi there...",
    "timestamp": "2024-03-15T12:00:00Z"
  }
}

Verify signatures

Always verify the X-Anima-Signature header before processing a webhook payload. Skipping verification lets anyone send arbitrary data to your endpoint.
Anima signs each request with HMAC-SHA256 using your webhook secret. The signature is sent in the X-Anima-Signature header as a hex digest of the raw request body.
import crypto from "crypto";
import express from "express";

const app = express();

app.post("/webhooks", express.raw({ type: "application/json" }), (req, res) => {
  const signature = req.headers["x-anima-signature"] as string;
  const secret = process.env.ANIMA_WEBHOOK_SECRET!;

  if (!verifyWebhook(req.body.toString(), signature, secret)) {
    return res.status(401).send("Invalid signature");
  }

  const event = JSON.parse(req.body.toString());

  switch (event.event) {
    case "message.received":
      console.log("New message:", event.data.subject);
      break;
    case "message.failed":
      console.error("Delivery failed for:", event.data.id);
      break;
  }

  res.status(200).send("OK");
});

function verifyWebhook(payload: string, signature: string, secret: string): boolean {
  const hmac = crypto.createHmac("sha256", secret);
  const digest = hmac.update(payload).digest("hex");
  return signature === digest;
}
Use hmac.compare_digest (Python) or crypto.timingSafeEqual (Node.js) for constant-time comparison to prevent timing attacks.

Retry behavior

If your endpoint returns a non-2xx response or times out, Anima retries the delivery with exponential backoff. Return 200 OK as quickly as possible and handle processing asynchronously if your logic is time-intensive.