Notification event types

Updated

Webhook notification events that the Conversational AI Platform sends for sessions, RTC, and telephony.

Preview documentation

You're reading the Conversational AI Platform (Preview) docs. Switch to Main.

When you enable Agora message notifications, the Agora notification server (NCS) sends event notifications about your Conversational AI Platform sessions to your server in HTTPS POST requests. This page describes the request format and each event type.

To receive notifications, configure a webhook for your project in Agora Console.

Choose event types by goal

The following table helps you find the events for common tasks:

GoalUse these event typesWhat they help you answer
Confirm that a session started or ended1001 Session state changedDid the agent start? When and why did the session end?
Get what was said in a session1002 Session transcriptsWhat did the user and the agent say?
Investigate failures1003 Session errors, 1001 Session state changedWhich component failed? Did the session end because of it?
Measure latency per turn1004 Turns finishedHow long did each turn take, and in which component?
Renew the agent's RTC token1101 RTC token expiringDoes the agent need a new token?
Track phone call progress1201 Telephony stateWas the call answered? How and why did it end?

For phone calls, the session and the call are reported separately. 1001 Session state changed reports the agent's session, which can be running while the call is still ringing. 1201 Telephony state reports the call itself. A call can also end before the session's post-call results are ready. To follow a phone call, handle both events.

Request header

Each notification request contains the following headers:

HeaderDescription
Content-Typeapplication/json
Agora-Signature-V2An HMAC/SHA256 signature over the raw request body. Use this signature when you can.
Agora-SignatureAn HMAC/SHA1 signature over the raw request body, for compatible integrations.

Verify the signature against the unchanged raw request body before you accept the notification.

Request body

The request body is a UTF-8 JSON object with the following fields:

FieldTypeRequiredDescription
noticeIdStringYesThe notification ID. It stays the same when NCS retries the same event. Use it to deduplicate notifications.
productIdIntegerYesThe product ID. For the Conversational AI Platform, 23.
eventTypeIntegerYesThe event type. See Event types.
notifyMsIntegerYesThe time NCS sent the notification, as a Unix timestamp in milliseconds. It can change when NCS retries.
sidStringYesThe session ID. It's the same as payload.agent_session_id.
payloadObjectYesThe event details. Each event type has its own fields.

The envelope fields use camelCase. The payload fields use snake_case, like the Session API. In payload, created_at and ended_at are the session's timestamps in Unix seconds, and occurred_at_ms is when the event happened, in Unix milliseconds.

{
  "noticeId": "notice-1001-example",
  "productId": 23,
  "eventType": 1001,
  "notifyMs": 1789913402100,
  "sid": "b42ac95tp25py49fc37hp94vm67ht52h",
  "payload": {
    "agent_session_id": "b42ac95tp25py49fc37hp94vm67ht52h",
    "status": "failed",
    "occurred_at_ms": 1789913402000,
    "created_at": 1789913400,
    "ended_at": 1789913402,
    "transport": {
      "type": "telephony",
      "direction": "outbound",
      "to_number": "+14155550101"
    }
  }
}

Respond to notifications

After you verify a notification, return HTTP status 200 with a JSON body within 10 seconds. Keep the following in mind:

  • Notifications can be duplicated or arrive out of order. Neither notifyMs nor occurred_at_ms sets the delivery order. Deduplicate by noticeId.
  • Acknowledge event types that you don't recognize, after you verify their signature.
  • If you miss a notification, Retrieve a session returns the session's latest status, not the transition you missed. Phone call state and post-call results can't be recovered this way.

Event types

Event types are grouped by scope: 10xx for sessions, 11xx for RTC, and 12xx for telephony. Session events apply to both transports.

Event typeEvent nameEvent description
1001Session state changedThe session status changes, including when the agent joins or leaves an RTC channel and when the session fails to start.
1002Session transcriptsTranscript processing for the session finished or failed.
1003Session errorsA component of the agent reported an error. The error doesn't necessarily end the session.
1004Turns finishedConversation turn data, including metrics for each turn, after the session ends.
1101RTC token expiringThe agent's RTC token is about to expire.
1201Telephony stateThe state of an inbound or outbound phone call changes.

1001 Session state changed

The session status changed. This event applies to both transports, and it includes failures before the agent joins an RTC channel or before a call is answered. For RTC sessions, running means the agent joined the channel, and stopped or failed means it left.

payload.status is the status at the transition. A later Retrieve a session request can return a newer status.

FieldTypeRequiredDescription
agent_session_idStringYesThe session ID. It's the same as the envelope sid.
agent_nameStringNoThe agent name, when it's known.
labelsObject<string, string>NoThe labels you passed when you started the session.
statusStringYesThe session status at this transition: idle, starting, running, stopping, stopped, or failed.
occurred_at_msIntegerYesWhen the transition happened, as a Unix timestamp in milliseconds.
created_atIntegerYesWhen the session was created, as a Unix timestamp in seconds.
ended_atIntegerNoWhen the session ended, as a Unix timestamp in seconds, if it's known.
transportObjectYesThe session's transport. For rtc, type and channel. For telephony, type, direction, and the from_number and to_number when they're known. It never contains RTC tokens or encryption settings.
messageStringNoDetails about the status, as free-form text, for example OK. Don't rely on a fixed set of values.

A session can fail before the phone numbers are resolved. In that case, transport doesn't contain from_number or to_number.

The following examples show payloads for RTC and phone sessions:

{
  "agent_session_id": "a42ac95tp25py49fc37hp94vm67ht52h",
  "agent_name": "support-agent",
  "status": "running",
  "occurred_at_ms": 1789913400000,
  "created_at": 1789913400,
  "transport": {
    "type": "rtc",
    "channel": "support-4821"
  },
  "labels": {
    "customer_group": "vip"
  }
}
{
  "agent_session_id": "a42ac95tp25py49fc37hp94vm67ht52h",
  "agent_name": "support-agent",
  "status": "stopped",
  "occurred_at_ms": 1789913470000,
  "created_at": 1789913400,
  "ended_at": 1789913470,
  "transport": {
    "type": "rtc",
    "channel": "support-4821"
  },
  "message": "OK",
  "labels": {
    "customer_group": "vip"
  }
}
{
  "agent_session_id": "c42ac95tp25py49fc37hp94vm67ht52h",
  "agent_name": "support-agent",
  "status": "running",
  "occurred_at_ms": 1789913401000,
  "created_at": 1789913400,
  "transport": {
    "type": "telephony",
    "direction": "outbound",
    "from_number": "+14155550100",
    "to_number": "+14155550101"
  },
  "labels": {
    "customer_group": "vip"
  }
}

1002 Session transcripts

Transcript processing for the session finished or failed. This event applies to both transports. The transcript records what the user and the agent said. It isn't the LLM's context window.

status is the result of transcript processing, not the session status. A phone call can end before this event arrives.

FieldTypeRequiredDescription
agent_session_idStringYesThe session ID. It's the same as the envelope sid.
occurred_at_msIntegerYesWhen the event happened, as a Unix timestamp in milliseconds. It isn't the NCS delivery time in notifyMs.
agent_nameStringNoThe agent name, when it's known.
labelsObject<string, string>NoThe labels you passed when you started the session.
created_atIntegerYesWhen the session was created, as a Unix timestamp in seconds.
ended_atIntegerYesWhen the session ended, as a Unix timestamp in seconds.
statusStringYesThe result of transcript processing: completed or failed.
transcriptArray<Object>YesThe spoken segments. Empty when processing failed or no speech was captured.
transcript[].roleStringYesThe speaker: user or assistant.
transcript[].contentStringYesThe words spoken or played. An interrupted agent segment contains only the part that was played.
transcript[].turn_idIntegerYesThe ID of the turn the segment belongs to.
transcript[].speech_started_msIntegerNoWhen the speech or playback started, as a Unix timestamp in milliseconds.
transcript[].speech_ended_msIntegerNoWhen the speech or playback ended, as a Unix timestamp in milliseconds.
transcript[].metadataObjectNoThe source of the segment in source: greeting, asr, llm, or command. An interrupted segment also contains interrupted, interrupted_ms, and original, the full text the agent planned to say.

The following example shows a payload for this event:

{
  "agent_session_id": "a42ac95tp25py49fc37hp94vm67ht52h",
  "occurred_at_ms": 1789913471000,
  "agent_name": "support-agent",
  "created_at": 1789913400,
  "ended_at": 1789913470,
  "status": "completed",
  "labels": {
    "customer_group": "vip"
  },
  "transcript": [
    {
      "role": "assistant",
      "content": "How can I help you today?",
      "turn_id": 1,
      "speech_started_ms": 1789913410000,
      "speech_ended_ms": 1789913411000,
      "metadata": {
        "source": "greeting"
      }
    },
    {
      "role": "user",
      "content": "Can you tell me a joke?",
      "turn_id": 2,
      "speech_started_ms": 1789913411200,
      "speech_ended_ms": 1789913411800,
      "metadata": {
        "source": "asr"
      }
    },
    {
      "role": "assistant",
      "content": "Why did the scarecrow ",
      "turn_id": 2,
      "speech_started_ms": 1789913412000,
      "speech_ended_ms": 1789913412700,
      "metadata": {
        "source": "llm",
        "interrupted": true,
        "interrupted_ms": 1789913412700,
        "original": "Why did the scarecrow win an award? Because he was outstanding in his field!"
      }
    },
    {
      "role": "user",
      "content": "Tell me a story instead.",
      "turn_id": 3,
      "speech_started_ms": 1789913412800,
      "speech_ended_ms": 1789913413400,
      "metadata": {
        "source": "asr"
      }
    },
    {
      "role": "assistant",
      "content": "Once upon a time in a land far away, there lived a brave knight.",
      "turn_id": 3,
      "speech_started_ms": 1789913413600,
      "speech_ended_ms": 1789913417000,
      "metadata": {
        "source": "llm"
      }
    },
    {
      "role": "assistant",
      "content": "Are you still there?",
      "turn_id": 4,
      "speech_started_ms": 1789913420000,
      "speech_ended_ms": 1789913421000,
      "metadata": {
        "source": "command"
      }
    }
  ]
}

1003 Session errors

A component of the agent reported an error. The error doesn't necessarily end the session. To find out whether the session ended, use 1001 Session state changed.

FieldTypeRequiredDescription
agent_session_idStringYesThe session ID. It's the same as the envelope sid.
occurred_at_msIntegerYesWhen the event happened, as a Unix timestamp in milliseconds. It isn't the NCS delivery time in notifyMs.
agent_nameStringNoThe agent name, when it's known.
created_atIntegerYesWhen the session was created, as a Unix timestamp in seconds.
turn_idIntegerNoThe ID of the turn the error belongs to, if any.
errorsArray<Object>YesThe errors reported in this notification.
errors[].moduleStringYesThe component that reported the error, for example llm.
errors[].turn_idIntegerNoThe ID of the turn the error belongs to, if any.
errors[].codeIntegerYesThe component's error code.
errors[].messageStringYesA description of the error.

The following example shows a payload for this event:

{
  "agent_session_id": "a42ac95tp25py49fc37hp94vm67ht52h",
  "occurred_at_ms": 1789913430000,
  "agent_name": "support-agent",
  "created_at": 1789913400,
  "turn_id": 2,
  "errors": [
    {
      "module": "llm",
      "turn_id": 2,
      "code": 503,
      "message": "Insufficient balance."
    }
  ]
}

1004 Turns finished

The session ended, and its conversation turn data is available. Each turn includes its start, its end, and its latency metrics, if they were collected. The payload can leave out earlier turns. is_truncated tells you whether it did.

Session timestamps are in Unix seconds. Turn boundaries are in Unix milliseconds.

FieldTypeRequiredDescription
agent_session_idStringYesThe session ID. It's the same as the envelope sid.
occurred_at_msIntegerYesWhen the event happened, as a Unix timestamp in milliseconds. It isn't the NCS delivery time in notifyMs.
agent_nameStringNoThe agent name, when it's known.
created_atIntegerYesWhen the session was created, as a Unix timestamp in seconds.
ended_atIntegerYesWhen the session ended, as a Unix timestamp in seconds.
total_turn_countIntegerYesThe total number of turns in the session.
is_truncatedBooleanYesWhether turns leaves out earlier turns.
turnsArray<Object>YesThe turns in this payload.
turns[].agent_session_idStringYesThe session ID.
turns[].turn_idIntegerYesThe turn ID.
turns[].startObjectYesThe start of the turn and what triggered it.
turns[].start.started_at_msIntegerYesWhen the turn started, as a Unix timestamp in milliseconds.
turns[].start.typeStringYesWhat started the turn: voice_input, greeting, silence_timeout, api_speak, or api_think.
turns[].start.metadataObjectNoDetails about what started the turn.
turns[].endObjectYesThe end of the turn and its outcome.
turns[].end.ended_at_msIntegerYesWhen the turn ended, as a Unix timestamp in milliseconds.
turns[].end.typeStringYesHow the turn ended: ok, error, ignored, or interrupted.
turns[].end.metadataObjectNoDetails about how the turn ended.
turns[].metricsObjectNoPerformance metrics for the turn, if collected.
turns[].metrics.e2e_latency_msNumberNoThe end-to-end latency of the turn, in milliseconds.
turns[].metrics.segmented_latency_msObject<string, number>NoThe latency of each component in milliseconds, such as asr_ttlw_ms, llm_ttfb_ms, and tts_ttfb_ms. Components that weren't measured are left out.

The following example shows a payload for this event:

{
  "agent_session_id": "a42ac95tp25py49fc37hp94vm67ht52h",
  "occurred_at_ms": 1789913473000,
  "agent_name": "support-agent",
  "created_at": 1789913400,
  "ended_at": 1789913470,
  "total_turn_count": 4,
  "is_truncated": true,
  "turns": [
    {
      "agent_session_id": "a42ac95tp25py49fc37hp94vm67ht52h",
      "turn_id": 4,
      "start": {
        "started_at_ms": 1789913420000,
        "type": "silence_timeout"
      },
      "end": {
        "ended_at_ms": 1789913421000,
        "type": "ok"
      },
      "metrics": {
        "e2e_latency_ms": 1500,
        "segmented_latency_ms": {
          "asr_ttlw_ms": 503,
          "llm_ttfb_ms": 1104,
          "tts_ttfb_ms": 85
        }
      }
    }
  ]
}

1101 RTC token expiring

The agent's RTC token is about to expire. This is a warning only: the agent stays in the channel, and the session continues. If you passed the agent's token when you started the session, update the session with a new transport.token before the token expires.

FieldTypeRequiredDescription
agent_session_idStringYesThe session ID. It's the same as the envelope sid.
occurred_at_msIntegerYesWhen the event happened, as a Unix timestamp in milliseconds. It isn't the NCS delivery time in notifyMs.
agent_nameStringNoThe agent name, when it's known.
transportObjectYesThe RTC transport. type is rtc, and channel is the channel name.
created_atIntegerYesWhen the session was created, as a Unix timestamp in seconds.
messageStringYesThe warning text: RTC token will expire soon.

The following example shows a payload for this event:

{
  "agent_session_id": "a42ac95tp25py49fc37hp94vm67ht52h",
  "occurred_at_ms": 1789913450000,
  "agent_name": "support-agent",
  "created_at": 1789913400,
  "transport": {
    "type": "rtc",
    "channel": "support-4821"
  },
  "message": "RTC token will expire soon."
}

1201 Telephony state

The state of an inbound or outbound phone call changed. Call state is separate from the session status: a running session doesn't mean that the call was answered. Each notification contains the basic call details. You can handle each one on its own, even when notifications arrive out of order.

FieldTypeRequiredDescription
agent_session_idStringYesThe session ID. It's the same as the envelope sid.
custom_idStringNoYour own call ID from the SIP X-switch-id header, if any. It isn't the session ID or an idempotency key.
directionStringYesThe call direction: inbound or outbound.
from_numberStringYesThe caller's number.
to_numberStringYesThe called number.
occurred_at_msIntegerYesWhen the event happened, as a Unix timestamp in milliseconds. It doesn't guarantee ordering.
stateStringYesThe call state: starting, calling, ringing, answered, transferred, or ended. A call can skip calling and ringing. Don't assume that a call was answered because it ended.
hangup_reasonStringNoWhy the call ended, as a normalized reason. Present when state is ended. See Hangup reasons.
hangup_reason_codeStringNoThe raw SIP or gateway reason code. Present when state is ended. New values can appear.
duration_secondsNumberNoHow long the call was connected, in seconds. 0 if it never connected.

The following example shows a payload for this event:

{
  "agent_session_id": "c42ac95tp25py49fc37hp94vm67ht52h",
  "custom_id": "customer-call-0001",
  "direction": "outbound",
  "from_number": "+14155550100",
  "to_number": "+14155550101",
  "state": "ended",
  "occurred_at_ms": 1789913470000,
  "hangup_reason": "USER_HANGUP",
  "hangup_reason_code": "NORMAL_CLEARING",
  "duration_seconds": 60
}

Hangup reasons

The following tables list the documented hangup_reason values and the hangup_reason_code values they map to, grouped by whether the call connected. Neither field is a closed list. If you get a hangup_reason_code you don't recognize, hangup_reason is UNKNOWN_REASON.

Reference

For more information, see the following pages:

API reference