Monitor Your Omniagent

Webhooks

Get notified on your server when sessions start and end, and when companions are created, updated, or deleted

A webhook sends an HTTP POST to your server whenever something happens in your project — a session starts or ends, or a companion is created, updated, or deleted. Use webhooks to react in real time instead of polling the API: record usage as sessions close, or update your own records when a companion finishes generating.

Creating a webhook

You create webhooks in the developer dashboard, per project.

Open the Webhooks page

In the dashboard, select your project and go to Webhooks, then click New webhook.

Configure the webhook

FieldDescription
Endpoint URLThe URL that receives the POST requests. Must be an https:// URL that's reachable from the internet.
EventsThe events that trigger this webhook. See Events.
StatusWhen enabled, the webhook receives events. Disable it to pause deliveries without deleting it.

Save the signing secret

Click Create webhook. The confirmation page shows the webhook's ID and its signing secret — a base64 string such as q3Zr8WmN1xTbV0yLc7sKd2Pf9uHaE4jRgO6nYwB5tQA=. Copy the secret and store it securely, for example in an environment variable on your server. You use it to verify that requests come from Napster.

The signing secret is shown only once. If you lose it, delete the webhook and create a new one.

A project can have one enabled webhook at a time. You need the organization Admin role to create, edit, or delete webhooks.

Events

EventWhen it's sent
session.startedA session connected and started.
session.closedA session that started has ended. Sessions that fail before starting don't send this event.
companion.createdA companion was created. Its status is pending while the avatar is generated.
companion.updatedA companion changed, including each status change while its avatar is generated.
companion.deletedA companion was deleted.

Payload format

Every event shares the same envelope. The event itself is in data, under session or companion:

{
  "eventId": "67d67303ec4f8672fefb704952206d48",
  "eventType": "companion.created",
  "timestamp": 1790685304,
  "organizationId": "org_abc123",
  "projectId": "proj_abc123",
  "data": {
    "companion": { }
  }
}
FieldDescription
eventIdA unique ID for the event. Use it to detect duplicate deliveries.
eventTypeThe event name, for example session.closed.
timestampWhen the event happened, as a Unix timestamp in seconds.
organizationIdThe organization the event belongs to.
projectIdThe project the event belongs to.
dataThe event payload — data.session for session events, data.companion for companion events.

Session events

session.started and session.closed describe the session in data.session:

{
  "eventId": "9f1c2e7a-4b3d-4f6e-8a1b-2c3d4e5f6a7b",
  "eventType": "session.closed",
  "timestamp": 1790690142,
  "organizationId": "org_abc123",
  "projectId": "proj_abc123",
  "data": {
    "session": {
      "id": "sess_xyz789",
      "type": "webrtc",
      "companionId": "comp_abc123",
      "externalClientId": "user_42",
      "createdAt": 1790689801,
      "startedAt": 1790689803,
      "closedAt": 1790690142,
      "reason": "connection_closed"
    }
  }
}
FieldDescription
idThe session ID. Use it with GET /public/sessions/{sessionId} to fetch the full session, including cost and transcript.
typeThe channel: webrtc, websocket, voip, or sip. Kiosk sessions report websocket.
companionIdThe companion the session used.
externalClientIdThe externalClientId you passed when opening the session, or null.
createdAt / startedAtWhen the session was created and when it started, as Unix timestamps in seconds.
closedAtWhen the session ended. session.closed only.
reasonWhy the session ended. session.closed only. See Close reasons.

session.closed doesn't include the session's cost. Billing is finalized as the session closes — fetch the session with its id to read cost.

Companion events

companion.created, companion.updated, and companion.deleted describe the companion in data.companion. For companion.deleted, it's the companion as it was before deletion. Companion events cover the companions your project owns, not the Napster stock catalog.

{
  "eventId": "e10b49a2b70d4657d728873dd473874c",
  "eventType": "companion.updated",
  "timestamp": 1790685858,
  "organizationId": "org_abc123",
  "projectId": "proj_abc123",
  "data": {
    "companion": {
      "id": "comp_abc123",
      "firstName": "Maya",
      "lastName": "Bennett",
      "previewUrl": "https://cdn.napster.com/companions/comp_abc123/preview.jpg",
      "videoLoopUrl": "https://cdn.napster.com/companions/comp_abc123/video.mp4",
      "ethnicity": "multiracial",
      "gender": "female",
      "headline": "Helping people find clarity, momentum, and practical next steps.",
      "tags": {},
      "status": "completed",
      "createdAt": 1790685277,
      "externalClientId": null,
      "versions": ["v1"]
    }
  }
}
FieldDescription
idThe companion ID.
firstName / lastNameThe companion's name.
previewUrlURL of the companion's preview image.
videoLoopUrlURL of the companion's idle video loop, or null until generation finishes.
ethnicity / genderThe companion's appearance attributes.
headlineThe companion's short description.
tagsKey–value tags on the companion.
statusGeneration status: pending, generationCompleted, readyToUse, or completed.
createdAtWhen the companion was created, as a Unix timestamp in seconds.
externalClientIdThe external client ID associated with the companion, or null.
versionsThe avatar model versions available for the companion, for example ["v1"]. Empty until generation finishes.

A new companion sends companion.created with status: "pending", then one companion.updated for each status change as it generates: generationCompleted, readyToUse, and finally completed. You can attach it to an agent from readyToUse; edit it once it's completed.

Verifying requests

Every delivery is signed with your webhook's signing secret. Verify the signature before trusting a request, so you only act on events that come from Napster.

Each request carries an X-Webhook-Signature header: the HMAC-SHA256 of the raw request body, keyed with your signing secret and hex-encoded.

POST /webhooks/napster HTTP/1.1
Content-Type: application/json
X-Webhook-Signature: 077820582aecb562633e656670fa842e81a391a0daebaa85f3bf71d39e933b9b

To verify a request, compute the same HMAC over the body you received and compare it to the header:

import express from "express";
import { createHmac, timingSafeEqual } from "node:crypto";

const app = express();

// express.raw keeps the body as the exact bytes Napster signed
app.post("/webhooks/napster", express.raw({ type: "application/json" }), (req, res) => {
  const expected = createHmac("sha256", process.env.NAPSTER_WEBHOOK_SECRET)
    .update(req.body)
    .digest("hex");
  const received = req.get("X-Webhook-Signature") ?? "";

  if (
    received.length !== expected.length ||
    !timingSafeEqual(Buffer.from(received), Buffer.from(expected))
  ) {
    return res.sendStatus(401);
  }

  const event = JSON.parse(req.body.toString("utf8"));
  res.sendStatus(200);
  // Process event.eventType after responding
});
import hashlib
import hmac
import os

from flask import Flask, abort, request

app = Flask(__name__)

@app.post("/webhooks/napster")
def napster_webhook():
    expected = hmac.new(
        os.environ["NAPSTER_WEBHOOK_SECRET"].encode(),
        request.get_data(),
        hashlib.sha256,
    ).hexdigest()
    received = request.headers.get("X-Webhook-Signature", "")

    if not hmac.compare_digest(expected, received):
        abort(401)

    event = request.get_json()
    # Process event["eventType"] after responding
    return "", 200

Sign the raw body exactly as received. Parsing the JSON and serializing it again changes the bytes, and the signature won't match. Use the signing secret exactly as shown in the dashboard: the base64 string itself is the key, so don't decode it.

Handling deliveries

A delivery succeeds when your endpoint responds with any 2xx status within 10 seconds. Any other status, a network error, or a timeout counts as a failed attempt.

A failed attempt is retried up to 3 more times with exponential backoff: about 1, 2, and 4 seconds between attempts. If every attempt fails, the delivery is marked failed and isn't retried again. You can still inspect it in the delivery history.

  • Respond quickly with a 2xx status. Do any slow work after you respond, so the delivery isn't treated as failed.
  • Expect duplicates. If your endpoint handles a request but responds too late, the retry delivers the same event again. Use eventId to skip events you've already processed.
  • Don't rely on order. Use timestamp to order events for the same session or companion.

Delivery history

The dashboard keeps a history of every delivery for each webhook. Open a webhook to see each delivery's event, status, and timestamp. Open a delivery to see the request that was sent — its body and headers — and each attempt, with the response status, body, and latency your endpoint returned. Response bodies are stored up to 4 KB.

Use it to debug your endpoint: if deliveries fail, the response your endpoint returned is recorded on each attempt.

Next steps

On this page