Skip to main content
When you create a new conversation and authorize a client session you can set custom metadata and custom headers on the session webhook configuration (either on the agent itself or per-session via a config override).

1) Set metadata and headers via session_webhook config

Add them to your agent’s session_webhook config, or send a one-off override in the config field when calling POST /v1/agents/web/authorize_session (see REST API docs). Both fields must be plain JSON objects. For agent.webhook plugin requests, set custom_headers / custom_metadata in the plugin options to have them included on every agent webhook call.
curl -X POST https://api.layercode.com/v1/agents/web/authorize_session \
  -H "Authorization: Bearer $LAYERCODE_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "agent_id": "dvv052qk",
    "config": {
      "type": "voice",
      "clients": {
        "browser": {
          "enabled": true
        }
      },
      "plugins": [
        { "use": "stt.deepgram", "options": { "model_id": "flux" } },
        { "use": "turn_manager", "options": { "mode": "automatic" } },
        { "use": "agent.llm", "options": { "provider": "google", "model_id": "gemini-2.5-flash-lite" } },
        { "use": "tts.rime", "options": { "model_id": "mistv2", "voice_id": "courtney" } }
      ],
      "session_webhook": {
        "url": "https://example.com/session-webhook",
        "events": ["session.start", "session.end", "session.update"],
        "custom_metadata": { "tenant_id": "t_42", "crm_contact_id": "abc-123" },
        "custom_headers": { "x-tenant-id": "t_42" }
      }
    }
  }'
Next.js app/api/authorize/route.ts
export const dynamic = "force-dynamic";
import { NextResponse } from "next/server";

export const POST = async (request: Request) => {
  // Here you could do any user authorization checks you need for your app
  const endpoint = "https://api.layercode.com/v1/agents/web/authorize_session";
  const apiKey = process.env.LAYERCODE_API_KEY;
  if (!apiKey) {
    throw new Error("LAYERCODE_API_KEY is not set.");
  }
  const requestBody = await request.json();
  if (!requestBody || !requestBody.agent_id) {
    throw new Error("Missing agent_id in request body.");
  }

  requestBody.config = {
    type: "voice",
    clients: {
      browser: {
        enabled: true
      }
    },
    plugins: [
      { use: "stt.deepgram", options: { model_id: "flux" } },
      { use: "turn_manager", options: { mode: "automatic" } },
      { use: "agent.llm", options: { provider: "google", model_id: "gemini-2.5-flash-lite" } },
      { use: "tts.rime", options: { model_id: "mistv2", voice_id: "courtney" } }
    ],
    session_webhook: {
      url: "https://example.com/session-webhook",
      events: ["session.start", "session.end", "session.update"],
      custom_metadata: {
        tenant_id: "t_42",
        crm_contact_id: "abc-123"
      },
      custom_headers: {
        "x-tenant-id": "t_42",
        "x-layercode-flow": "concierge"
      }
    }
  }

  try {
    const response = await fetch(endpoint, {
      method: "POST",
      headers: {
        "Content-Type": "application/json",
        Authorization: `Bearer ${apiKey}`,
      },
      body: JSON.stringify(requestBody),
    });
    if (!response.ok) {
      const text = await response.text();
      throw new Error(text || response.statusText);
    }
    return NextResponse.json(await response.json());
  } catch (error: any) {
    console.log("Layercode authorize session response error:", error.message);
    return NextResponse.json({ error: error.message }, { status: 500 });
  }
};

2) Read the metadata in your webhook handler

Every webhook invocation now includes the metadata you provided:
{
  "type": "message",
  "session_id": "a0ad4pv43sdakgh99lxeik8x",
  "conversation_id": "ary4c07o15n5l43iu7dkhukt",
  "custom_metadata": {
    "tenant_id": "t_42",
    "crm_contact_id": "abc-123"
  },
  // ...
}

3) Inspect custom headers on receipt

Layercode prepends two headers to every webhook call: Content-Type: application/json and layercode-signature. Your custom headers are then appended.
webhook-handler.ts
export async function POST(request: Request) {
  const xTenantId = request.headers.get('x-tenant-id');
  // ...
  const payload = await request.json();
  // ...
}