# Webhooks (/en/realtime-media/cloud-recording/build/handle-events/receive-notifications)

> For AI agents: see the complete documentation index at [llms.txt](/llms.txt).

A webhook is a user-defined callback over HTTPS that allows your app or back-end system to receive notifications when certain events occur. Agora calls your webhook endpoint from its servers to send notifications about Cloud Recording events. With Notifications, you can subscribe to Cloud Recording events and receive notifications in real time.

## Understand the tech [#understand-the-tech]

Using Agora Console you subscribe to specific events for your project and configure the URL of the webhooks to receive these events. Agora sends notifications of your events to your webhook every time they occur. Your server authenticates the notification and returns `200 Ok` to confirm reception. You use the information in the JSON payload of each notification to give the best user experience to your users.

The following figure illustrates the workflow when Notifications is enabled for the specific Cloud Recording events you subscribe to:

![cloud-recording](https://assets-docs.agora.io/images/notification-center-service/ncs-cloud-recording-workflow.svg)

1. A user commits an action that creates an event.
2. Notifications sends an HTTPS POST request to your webhook.
3. Your server validates the request signature, then sends a response to Notifications within 10 seconds. The response body must be in JSON.

If Notifications receives `200 OK` within 10 seconds of sending the initial notification, the callback is considered successful. If these conditions are not met, Notifications immediately resends the notification. The interval between notification attempts gradually increases. Notifications stops sending notifications after three retries.

## Prerequisites [#prerequisites]

To set up and use Notifications, you must have:

* A [valid Agora account](../../get-started/manage-agora-account#create-an-agora-account).
* An [active Agora project](../../get-started/manage-agora-account#create-an-agora-project).
* A computer with Internet access.

  If your network access is restricted by a firewall, call the [IP address query API](#ip-address-query-api) to retrieve the Notifications IP addresses , then configure the firewall to allow these IP addresses.

## Handle notifications for specific events [#handle-notifications-for-specific-events]

In order to handle notifications for the events you subscribe to, you need to:

* [Create your webhook](#create-your-webhook)
* [Enable Notifications](#enable-notifications)
* [Verify Notifications signatures](#add-signature-verification)

### Create your webhook [#create-your-webhook]

Once Notifications is enabled, Agora SDRTN® sends notification callbacks as `HTTPS POST` requests to your webhook when events that you are subscribed to occur. The data format of the requests is JSON, the character encoding is `UTF-8`, and the signature algorithm is `HMAC/SHA1` or `HMAC/SHA256`.

For Notifications, a webhook is an endpoint on an `HTTPS` server that handles these requests. In a production environment you write this in your web infrastructure, for development purposes best practice is to create a simple local server and use a service such as [ngrok](https://ngrok.com/download) to supply a public URL that you register with Agora SDRTN® when you enable Notifications.

To do this, take the following steps:

1. **Set up Go**

   Ensure you have Go installed on your system. If not, download and install it from the [official Go website](https://go.dev/dl/).

2. **Create a Go project for your server**

   Create a new directory for your project and navigate into it:

   ```sh
   mkdir agora-webhook-server
   cd agora-webhook-server
   ```

   In the project directory, create a new file `main.go`. Open the file in your preferred text editor and add the following code:

   ```go
   package main

   import (
     "encoding/json"
     "fmt"
     "io"
     "log"
     "net/http"
   )

   type WebhookRequest struct {
       NoticeID   string `json:"noticeId"`
       ProductID  int64  `json:"productId"`
       EventType  int    `json:"eventType"`
       Payload    Payload `json:"payload"`
   }

   type Payload struct {
       ClientSeq   int64  `json:"clientSeq"`
       UID         int    `json:"uid"`
       ChannelName string `json:"channelName"`
   }

   func rootHandler(w http.ResponseWriter, r *http.Request) {
       response := `<h1>Agora Notifications demo</h1>`
       w.WriteHeader(http.StatusOK)
       w.Write([]byte(response))
   }

   func ncsHandler(w http.ResponseWriter, r *http.Request) {
       agoraSignature := r.Header.Get("Agora-Signature")
       fmt.Println("Agora-Signature:", agoraSignature)

       body, err := io.ReadAll(r.Body)
       if err != nil {
           http.Error(w, "Unable to read request body", http.StatusBadRequest)
           return
       }

       var req WebhookRequest
       if err := json.Unmarshal(body, &req); err != nil {
           http.Error(w, "Invalid JSON", http.StatusBadRequest)
           return
       }

       fmt.Printf("Event code: %d Uid: %d Channel: %s ClientSeq: %d\n",
           req.EventType, req.Payload.UID, req.Payload.ChannelName, req.Payload.ClientSeq)

       w.WriteHeader(http.StatusOK)
       w.Write([]byte("Ok"))
   }

   func main() {
       http.HandleFunc("/", rootHandler)
       http.HandleFunc("/ncsNotify", ncsHandler)

       port := ":8080"
       fmt.Printf("Notifications webhook server started on port %s\n", port)
       if err := http.ListenAndServe(port, nil); err != nil {
           log.Fatalf("Failed to start server: %v", err)
       }
   }
   ```

3. **Run your Go server**

   Run the server using the following command:

   ```sh
   go run main.go
   ```

4. **Create a public URL for your server**

   In this example you use `ngrok` to create a public URL for your server.

   1. Download and install [ngrok](https://ngrok.com/download). If you have `Chocolatey`, use the following command:

      ```bash
      choco install ngrok
      ```

   2. Add an `authtoken` to ngrok:

      ```bash
      ngrok config add-authtoken <authToken>
      ```

      To obtain an `authToken`, [sign up](https://dashboard.ngrok.com/signup) with ngrok.

   3. Start a tunnel to your local server using the following command:

      ```bash
      ngrok http 127.0.0.1:8080
      ```

      You see a **Forwarding** URL and a **Web Interface** URL in the console. Open the web interface URL in your browser.

5. **Test the server**

   Open a web browser and navigate to the public URL provided by `ngrok` to see the root handler response.

   Use curl, [Postman](https://www.postman.com/), or another tool to send a `POST` request to `https://<ngrok_url>/ncsNotify` with the required `JSON` payload.

   Example using `curl`:

   ```sh
   curl -X POST <ngrok_url>/ncsNotify \
   -H "Content-Type: application/json" \
   -H "Agora-Signature: your_signature" \
   -d '{
     "noticeId": "some_notice_id",
     "productId": 12345,
     "eventType": 1,
     "payload": {
       "clientSeq": 67890,
       "uid": 123,
       "channelName": "test_channel"
     }
   }'
   ```

   Make sure you replace `ngrok_url` with the forwarding url.

   Once the HTTP request is successful, you see the following `JSON` payload in your browser:

   ```json
   {
     "noticeId": "some_notice_id",
     "productId": 12345,
     "eventType": 1,
     "payload": {
       "clientSeq": 67890,
       "uid": 123,
       "channelName": "test_channel"
     }
   }
   ```

### Enable Notifications [#enable-notifications]

To enable Notifications:

1. Log in to [Agora Console](https://console.agora.io/v2). On the **Projects** tab, locate the project for which you want to enable Notifications and click **Edit**.

   ![Project tab](https://assets-docs.agora.io/images/video-sdk/enable-ncs-project-tabs.png)

2. In the **All Features** section, open the **Notifications** tab and click on the service for which you want to enable notifications. The section expands to show configuration options.

   ![Notification tab](https://assets-docs.agora.io/images/video-sdk/enable-ncs-notification-tab.png)

3. Fill in the following information:

   * **Event**: Select all the events that you want to subscribe to.

     If the selected events generate a high number of queries per second (QPS), ensure that your server has sufficient processing capacity.

   * **Receiving Server Region**: Select the region where your server that receives the notifications is located. Agora connects to the nearest Agora node server based on your selection.

   * **Receiving Server URL Endpoint**: The `HTTPS` public address of your server that receives the notifications. For example, `https://1111-123-456-789-99.ap.ngrok.io/ncsNotify`.

<CalloutContainer type="info">
  <CalloutDescription>
    For enhanced security, Notifications no longer supports `HTTP` addresses.
  </CalloutDescription>
</CalloutContainer>

* To reduce the delay in notification delivery, best practice is to activate `HTTP` persistent connection (also called `HTTP` keep-alive) on your server with the following settings:

  * `MaxKeepAliveRequests`: 100 or more
  * `KeepAliveTimeout`: 10 seconds or more

* **Whitelist**: If your server is behind a firewall, check the box here, and ensure that you call the [IP address query API](#ip-address-query-api) to get the IP addresses of the Agora Notifications server and add them to the firewall's allowed IP list.

![Notification tab](https://assets-docs.agora.io/images/video-sdk/enable-ncs-configuration-tab.png)

4. Copy the **Secret** displayed against the product name by clicking the copy icon. You use this secret to [Add signature verification](#add-signature-verification).

5. Press **Check**. Agora performs a health test for your configuration as follows:

   1. The Notifications health test generates test events that correspond to your subscribed events, and then sends test event callbacks to your server.

   2. After receiving each test event callback, your server must respond within 10 seconds with a status code of `200`. The response body must be in JSON format.

   3. When the Notifications health test succeeds, read the prompt and press **Apply Settings**. After your configuration is saved, the **Status** of Notifications shows **Enabled**.

      ![Apply Settings](https://assets-docs.agora.io/images/video-sdk/enable-ncs-apply-settings.png)

      If the Notifications health test fails, follow the prompt on the Agora Console to troubleshoot the error. Common errors include the following:

      * Request timeout (590): Your server does not return the status code `200` within 10 seconds. Check whether your server responds to the request properly. If your server responds to the request properly, contact Agora Technical Support to check if the network connection between the Agora Notifications server and your server is working.

      * Domain name unreachable (591): The domain name is invalid and cannot be resolved to the target IP address. Check whether your server is properly deployed.

      * Certificate error (592): The Agora Notifications server fails to verify the SSL certificates returned by your server. Check if the SSL certificates of your server are valid. If your server is behind a firewall, check whether you have added all IP addresses of the Agora Notifications server to the firewall's allowed IP list.

      * Other response errors: Your server returns a response with a status code other than `200`. See the prompt on the Agora Console for the specific status code and error messages.

<Accordions>
  <Accordion title="Video walkthrough">
    <video src="https://assets-docs.agora.io/images/video-sdk/enable-notifications.mp4" style="{ width: '100%', height: 'auto' }">
      Your browser does not support the <code>video</code> element.
    </video>
  </Accordion>
</Accordions>

### Add signature verification [#add-signature-verification]

To communicate securely between Notifications and your webhook, Agora SDRTN®  uses signatures for identity verification as follows:

1. When you configure Notifications in Agora Console, Agora SDRTN®  generates a secret you use for verification.
2. When sending a notification, Notifications generates two signature values from the secret using `HMAC/SHA1` and `HMAC/SHA256` algorithms. These signatures are added as `Agora-Signature` and `Agora-Signature-V2` to the `HTTPS` request header.
3. When your server receives a callback, you can verify `Agora-Signature` or `Agora-Signature-V2`:

   * To verify `Agora-Signature`, use the secret, the raw request body, and the `crypto/sha1` algorithm.
   * To verify `Agora-Signature-V2`, use the secret, the raw request body, and the `crypto/sha256` algorithm.

The following sample code uses `crypto/sha1`.

To add signature verification to your server, take the following steps:

1. In the `main.go` file, replace your imports with the following:

   ```go
   import (
       "crypto/hmac"
       "crypto/sha1"
       "encoding/hex"
       "encoding/json"
       "fmt"
       "io"
       "log"
       "net/http"
   )
   ```

2. Add the following code after the list of imports:

   ```go
   // Replace with your NCS secret
   const secret = "<Replace with your secret code>"

   // calcSignatureV1 computes the HMAC/SHA256 signature for a given payload and secret
   func calcSignatureV1(secret, payload string) string {
       mac := hmac.New(sha1.New, []byte(secret))
       mac.Write([]byte(payload))
       return hex.EncodeToString(mac.Sum(nil))
   }

   // verify checks if the provided signature matches the HMAC/SHA256 signature of the request body
   func verify(requestBody, signature string) bool {
       calculatedSignature := calcSignatureV1(secret, requestBody)
       fmt.Println("Calculated Signature:", calculatedSignature)
       fmt.Println("Received Signature:", signature)
       return calculatedSignature == signature
   }
   ```

3. In `main.go`, add the following code after you read the request body:

   ```go
   // Verify the signature
   if !verify(string(body), agoraSignature) {
       http.Error(w, "Invalid signature", http.StatusUnauthorized)
       return
   }
   ```

4. To test the server, follow the steps given in the [Enable notifications](#enable-notifications) section.

5. When you receive an event from the console, and if the signature matches, the event details are displayed in your browser.

## Reference [#reference]

This section contains in depth information about Notifications.

### Request Header [#request-header]

The header of notification callbacks contains the following fields:

| Field name           | Value                                                                                                                                                                                                                                                |
| :------------------- | :--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `Content-Type`       | `application/json`                                                                                                                                                                                                                                   |
| `Agora-Signature`    | The signature generated by Agora using the **Secret** and the HMAC/SHA1 algorithm. You need to use the Secret and HMAC/SHA1 algorithm to verify the signature value. For details, see [Signature verification](#add-signature-verification).         |
| `Agora-Signature-V2` | The signature generated by Agora using the **Secret** and the HMAC/SHA256 algorithm. You need to use the Secret and the HMAC/SHA256 algorithm to verify the signature value. For details, see [Signature verification](#add-signature-verification). |

### Request Body [#request-body]

The request body of notification callbacks contains the following fields:

| Field name  | Type        | Description                                                                                                                                             |
| :---------- | :---------- | :------------------------------------------------------------------------------------------------------------------------------------------------------ |
| `noticeId`  | String      | The notification ID, identifying the notification callback when the event occurs.                                                                       |
| `productId` | Number      | The product ID:- `1`: Realtime Communication (RTC) service
- `3`: Cloud Recording
- `4`: Media Pull
- `5`: Media Push                                   |
| `eventType` | Number      | The type of event being notified. For details, see [event types](#event-types).                                                                         |
| `notifyMs`  | Number      | The Unix timestamp (ms) when Notifications sends a callback to your server. This value is updated when Notifications resends the notification callback. |
| `payload`   | JSON Object | The content of the event being notified. The payload varies with event type.                                                                            |

#### Example [#example]

```json
{
   "noticeId":"2000001428:4330:107",
   "productId":1,
   "eventType":101,
   "notifyMs":1611566412672,
   "payload":{
      ...
   }
}
```

### Event types [#event-types]

You can subscribe to the following Cloud Recording events from Agora Console:

| eventType                                     | serviceType                   | Event description                                                                                                             |
| :-------------------------------------------- | :---------------------------- | :---------------------------------------------------------------------------------------------------------------------------- |
| [1](#1-cloud_recording_error)                 | 0 (cloud recording service)   | An error occurs during the recording.                                                                                         |
| [2](#2-cloud_recording_warning)               | 0 (cloud recording service)   | A warning occurs during the recording.                                                                                        |
| [3](#3-cloud_recording_status_update)         | 0 (cloud recording service)   | The status of the Agora Cloud Recording service changes.                                                                      |
| [4](#4-cloud_recording_file_infos)            | 0 (cloud recording service)   | The M3U8 playlist file is generated.                                                                                          |
| [11](#11-session_exit)                        | 0 (cloud recording service)   | The cloud recording service has ended its tasks and exited.                                                                   |
| [12](#12-session_failover)                    | 0 (cloud recording service)   | The cloud recording service has enabled the [high availability mechanism](/en/realtime-media/cloud-recording).                |
| [30](#30-uploader_started)                    | 2 (uploader module)           | The upload service starts.                                                                                                    |
| [31](#31-uploaded)                            | 2 (uploader module)           | All the recorded files are uploaded to the specified third-party cloud storage.                                               |
| [32](#32-backuped)                            | 2 (uploader module)           | All the recorded files are uploaded, but at least one file is uploaded to Agora Cloud Backup.                                 |
| [33](#33-uploading_progress)                  | 2 (uploader module)           | The progress of uploading the recorded files to the cloud storage.                                                            |
| [40](#40-recorder_started)                    | 1 (recorder module)           | The recording starts.                                                                                                         |
| [41](#41-recorder_leave)                      | 1 (recorder module)           | The recording exits.                                                                                                          |
| [42](#42-recorder_slice_start)                | 1 (recorder module)           | The recording service syncs the information of the recorded files.                                                            |
| [43](#43-recorder_audio_stream_state_changed) | 1 (recorder module)           | The state of the audio stream changes.                                                                                        |
| [44](#44-recorder_video_stream_state_changed) | 1 (recorder module)           | The state of the video stream changes.                                                                                        |
| [45](#45-recorder_snapshot_file)              | 1 (recorder module)           | The screenshot is captured successfully.                                                                                      |
| [60](#60-vod_started)                         | 4 (extension services)        | The uploader for ApsaraVideo for VoD has started and successfully acquired the upload credential.                             |
| [61](#61-vod_triggered)                       | 4 (extension services)        | All recorded files have been uploaded to ApsaraVideo for VoD.                                                                 |
| [70](#70-web_recorder_started)                | 6 (web page recording module) | Web page recording starts.                                                                                                    |
| [71](#71-web_recorder_stopped)                | 6 (web page recording module) | Web page recording stops.                                                                                                     |
| [72](#72-web_recorder_capability_limit)       | 6 (web page recording module) | The web page to record uses a feature that is unsupported by web page recording. The recording service will stop immediately. |
| [73](#73-web_recorder_reload)                 | 6 (web page recording module) | The web page reloads.                                                                                                         |
| [90](#90-download_failed)                     | 8（download module）            | The recording service fails to download the recorded files.                                                                   |
| [100](#100-rtmp_publish_status)               | 6（web page recording module）  | The CDN streaming status of the web page recording changes.                                                                   |
| [1001](#1001-postpone_transcode_final_result) | 0 (cloud recording service)   | The result of the transcoding.                                                                                                |

#### Fields in payload [#fields-in-payload]

`payload` is a JSON object, which is the main body of the notification. `payload` in each type of event notification includes the following fields:

* `cname`: String. The name of the channel to be recorded.
* `uid`: String. The user ID of the recording client.
* `sid`: String. The recording ID. The unique identifier of each recording.
* `sequence`: Number. The serial number of the notifications, starting from 0. You can use this parameter to identify notifications that are random, lost, or resent.
* `sendts`: Number. The time (UTC) when the event happens. Unix timestamp in ms.
* `serviceType`: Number. The type of Agora service.
  * `0`: The cloud recording service.
  * `1`: The recorder module.
  * `2`: The uploader module.
  * `4`: The extension services.
  * `6`: The web page recording module.
  * `8`: The download module.
* `details`: JSON. The details of the callback events are described in the following section. Notifications may add new fields to `details` in the future. To ensure backward compatibility, the service will not change the data format of existing fields.

The relevant fields of each Agora Cloud Recording callback event are listed below:

#### cloud\_recording\_error [#cloud_recording_error]

`eventType` 1 indicates that an error occurs during the recording. `details` includes the following fields:

* `msgName`: String. The name of the message, `"cloud_recording_error"`.
* `module`: Number. The module in which the error occurs.
  * `0`: The recorder
  * `1`: The uploader
  * `2`: The Agora Cloud Recording Service
  * `6`：The Agora Web Page Recording Service
* `errorLevel`: Number. The error level.
  * `1`: Debug
  * `2`: Minor
  * `3`: Medium
  * `4`: Major
  * `5`: Fatal. A fatal error may cause the recording to exit. If you receive a message of this level, call `query` to check the current status and process the error according to the error notifications.
* `errorCode`: Number. The error code.
  * If the error occurs in the recorder module (`0`), see [common errors](/en/realtime-media/cloud-recording/reference/common-errors).
  * If the error occurs in the uploader (`1`), see [upload error code](#uploaderr).
  * If the error occurs in the Agora Cloud Recording Service (`2`), see [cloud recording service error code](#clouderr).
  * If the error occurs in the other modules, see [common errors](/en/realtime-media/cloud-recording/reference/common-errors).
    If you do not find any error code in the above-mentioned pages, contact our technical support.
* `stat`: Number. The event status. 0 indicates normal status; other values indicate abnormal status.
* `errorMsg`: String. The detailed description of the error.

#### cloud\_recording\_warning [#cloud_recording_warning]

`eventType` 2 indicates that a warning occurs during the recording. `details` includes the following fields:

* `msgName`: String. The message name, `cloud_recording_warning`.
* `module`: Number. The name of the module where the warning occurs.
  * `0`: The recorder.
  * `1`: The uploader.
* `warnCode`: Number. The warning code.
* If the warning occurs in the recorder, see [warning code](#cloudrecordingwarning).
* If the warning occurs in the uploader, see [upload warning code](#uploadwarn).

#### cloud\_recording\_status\_update [#cloud_recording_status_update]

`eventType` 3 indicates that the method call fails because it does not match the status of the cloud recording service. For example, the method call of `start` fails if the service has already started. `details` includes the following fields:

* `msgName`: String. The message name, `cloud_recording_status_update`.
* `status`: Number. The status of the cloud recording service. See [Status codes for the Agora Cloud Recording service](#state) for details.
* `recordingMode`: Number. The recording mode:
  * `0`: Individual recording mode
  * `1`: Composite recording mode
* `fileList`: String. The name of the M3U8 index file generated by the service.

#### cloud\_recording\_file\_infos [#cloud_recording_file_infos]

`eventType` 4 indicates that an M3U8 playlist file is generated and uploaded. During a recording, the recording service repeatedly uploads and overwrites the M3U8 file, but this event is triggered the first time the M3U8 file is generated and uploaded.

`details` includes the following fields:

* `msgName`: String. The message name, `cloud_recording_file_infos`.
* `fileList`: String. The name of the M3U8 file.

#### session\_exit [#session_exit]

`eventType` 11 indicates that the cloud recording service has ended its tasks and exited. `details` includes the following fields:

* `msgName`: String. The message name, `session_exit`.
* `exitStatus`: Number. The exit status.
  * `0`: A normal exit, indicating that the recording service exits after the recording ends and the recorded files are uploaded.
  * `1`: An abnormal exit. An abnormal exist occurs when, for example, a parameter is incorrectly set.

#### session\_failover [#session_failover]

`eventType` 12 indicates that the cloud recording service has enabled the high availability mechanism, where the fault processing center automatically switches to a new server within 90 seconds to resume the service. `details` includes the following fields:

* `msgName`: String. The message name, `session_failover`.
* `newUid`: Number. A randomly-generated recording user ID. After the high availability mechanism is enabled and the service is switched to a new server, the service rejoins the channel with a randomly-generated recording user ID, abandoning the old one.

#### uploader\_started [#uploader_started]

`eventType` 30 indicates that the upload service starts, and `details` includes the following fields:

* `msgName`: String. The message name, `uploader_started`.
* `status`: Number. The event status. 0 indicates normal status; other values indicate abnormal status.

#### uploaded [#uploaded]

`eventType` 31 indicates that all the recorded files are uploaded to the specified third-party cloud storage, and `details` includes the following fields:

* `msgName`: String. The message name, `uploaded`.
* `status`: Number. The event status. 0 indicates normal status; other values indicate abnormal status.
* `fileList`: JSONArray. An array that contains detailed information of each recorded file.
  * `fileName`: String. The name of the M3U8 file or MP4 file.
  * `trackType`: String. The type of the recorded file.
    * `"audio"`: Audio file.
    * `"video"`: Video file (no audio).
    * `"audio_and_video"`: Video file (with audio).
  * `uid`: String. The user ID. The user whose audio or video is recorded in the file. In composite recording mode, `uid` is `"0"`.
  * `mixedAllUser`: Boolean. Whether the audio and video of all users are combined into a single file.
    * `true`: All users are recorded in a single file.
    * `false`: Each user is recorded separately.
  * `isPlayable`: Boolean. Whether the file can be played online.
    * `true`: The file can be played online.
    * `false`: The file cannot be played online.
  * `sliceStartTime`: Number. The Unix time (ms) when the recording starts.

#### Examples [#examples]

* Individual recording:

  ```json
  {
    "msgName": "uploaded",
    "fileList": [
      {
        "fileName": "xxx.m3u8",
        "trackType": "audio",
        "uid": "57297",
        "mixedAllUser": false,
        "isPlayable": true,
        "sliceStartTime": 1619172871089
      },
      {
        "fileName": "xxx.m3u8",
        "trackType": "audio",
        "uid": "10230",
        "mixedAllUser": false,
        "isPlayable": true,
        "sliceStartTime": 1619172871099
      }
    ],
    "status": 0
  }
  ```

* Composite recording with no MP4 files generated:

  ```json
  {
    "msgName": "uploaded",
    "fileList": [
      {
        "fileName": xxx.m3u8",
        "trackType": "audio_and_video",
        "uid": "0",
        "mixedAllUser": true,
        "isPlayable": true,
        "sliceStartTime": 1619170461821
      }
    ],
    "status": 0
  }
  ```

* Composite recording with MP4 files generated:

  ```json
  {
    "msgName": "uploaded",
    "fileList": [
      {
        "fileName": "xxx.mp4",
        "trackType": "audio_and_video",
        "uid": "0",
        "mixedAllUser": true,
        "isPlayable": true,
        "sliceStartTime": 1619172632080
      },
      {
        "fileName": "xxx.m3u8",
        "trackType": "audio_and_video",
        "uid": "0",
        "mixedAllUser": true,
        "isPlayable": true,
        "sliceStartTime": 1619172632080
      }
    ],
    "status": 0
  }
  ```

* Web page recording with MP4 files generated:

  ```json
  {
    "msgName": "uploaded",
    "status": 0
  }
  ```

#### backuped [#backuped]

`eventType` 32 indicates that all the recorded files are uploaded, but at least one of them is uploaded to Agora Cloud Backup. Agora Cloud Backup automatically uploads the files to the specified third-party cloud storage. `details` includes the following fields:

* `msgName`: String. The message name, `backuped`.
* `status`: Number. The event status. 0 indicates normal status; other values indicate abnormal status.
* `fileList`: JSONArray. An array that contains detailed information of each recorded file.
  * `fileName`: String. The name of the M3U8 file or MP4 file.
  * `trackType`: String. The type of the recorded file.
    * `"audio"`: Audio file.
    * `"video"`: Video file (no audio).
    * `"audio_and_video"`: Video file with audio.
  * `uid`: String. The user ID. The user whose audio or video is recorded in the file. In composite recording mode, `uid` is `"0"`.
  * `mixedAllUser`: Boolean. Whether the audio and video of all users are combined into a single file.
    * `true`: All users are recorded in a single file.
    * `false`: Each user is recorded separately.
  * `isPlayable`: Boolean. Whether the file can be played online.
    * `true`: The file can be played online.
    * `false`: The file cannot be played online.
  * `sliceStartTime`: Number. The Unix time (ms) when the recording starts.

#### Examples [#examples-1]

* Individual recording:

  ```json
  {
    "msgName": "backuped",
    "fileList": [
      {
        "fileName": "xxx.m3u8",
        "trackType": "audio",
        "uid": "57297",
        "mixedAllUser": false,
        "isPlayable": true,
        "sliceStartTime": 1619172871089
      },
      {
        "fileName": "xxx.m3u8",
        "trackType": "audio",
        "uid": "10230",
        "mixedAllUser": false,
        "isPlayable": true,
        "sliceStartTime": 1619172871099
      }
    ],
    "status": 0
  }
  ```

* Composite recording with no MP4 files generated:

  ```json
  {
    "msgName": "backuped",
    "fileList": [
      {
        "fileName": "xxx.m3u8",
        "trackType": "audio_and_video",
        "uid": "0",
        "mixedAllUser": true,
        "isPlayable": true,
        "sliceStartTime": 1619170461821
      }
    ],
    "status": 0
  }
  ```

* Composite recording with MP4 files generated:

  ```json
  {
    "msgName": "backuped",
    "fileList": [
      {
        "fileName": "xxx.mp4",
        "trackType": "audio_and_video",
        "uid": "0",
        "mixedAllUser": true,
        "isPlayable": true,
        "sliceStartTime": 1619172632080
      },
      {
        "fileName": "xxx.m3u8",
        "trackType": "audio_and_video",
        "uid": "0",
        "mixedAllUser": true,
        "isPlayable": true,
        "sliceStartTime": 1619172632080
      }
    ],
    "status": 0
  }
  ```

* Web page recording with MP4 files generated:

  ```json
  {
    "msgName": "backuped",
    "status": 0
  }
  ```

#### uploading\_progress [#uploading_progress]

`eventType` 33 indicates the current upload progress. The Agora server notifies you of the upload progress once every minute after the recording starts. `details` includes the following fields:

* `msgName`: String. The message name, `uploading_progress`.
* `progress`: Number. An ever-increasing number between 0 and 10,000, equal to the ratio of the number of the uploaded files to the number of the recorded files multiplied by 10,000. After the recording service exits and when the number reaches 10,000, the upload completes.

#### recorder\_started [#recorder_started]

`eventType` 40 indicates that the recording service starts, and `details` includes the following fields:

* `msgName`: String. The message name, `recorder_started`.
* `status`: Number. The event status. 0 indicates normal status; other values indicate abnormal status.

#### recorder\_leave [#recorder_leave]

`eventType` 41 indicates that the recorder leaves the channel, and `details` includes the following fields:

* `msgName`: String. The message name, `recorder_leave`.
* `leaveCode`: Number. The leave code. You can perform a bitwise AND operation on the code and each enum value, and those with non-zero results are the reason for the exit. For example, if you perform a bit-by-bit AND operation on code 6 (binary 110) and each enum value, only LEAVE\_CODE\_SIG (binary 10) and LEAVE\_CODE\_NO\_USERS (binary 100) get a non-zero result. The reasons for exiting, in this case, include a timeout and a signal triggering the exit.
  \| Enumerator              |                                                              |
  \| :---------------------- | ------------------------------------------------------------ |
  \| LEAVE\_CODE\_INIT         | 0: The initialization fails.                                 |
  \| LEAVE\_CODE\_SIG          | 2 (binary 10): The AgoraCoreService process receives the SIGINT signal.  |
  \| LEAVE\_CODE\_NO\_USERS     | 4 (binary 100): The recording server automatically leaves the channel and stops recording because the channel has no user. |
  \| LEAVE\_CODE\_TIMER\_CATCH  | 8 (binary 1000): Ignore it.                                                |
  \| LEAVE\_CODE\_CLIENT\_LEAVE | 16 (binary 10000): The recording server calls the `leaveChannel` method to leave the channel. |

#### recorder\_slice\_start [#recorder_slice_start]

`eventType` 42 indicates that the recording service syncs the information of the recorded files, and `details` includes the following fields:

* `msgName`: String. The message name, `recorder_slice_start`.
* `startUtcMs`：Number. The time (ms) in UTC when the recording starts (the starting time of the first slice file).
* `discontinueUtcMs`：Number. In most cases, this field is the same as `startUtcMs`. When the recording is interrupted, the Agora Cloud Recording service automatically resumes the recording and triggers this event. In this case, the value of this field is the time (ms) in UTC when the last slice file stops.
* `mixedAllUser`: Boolean. Whether the audio and video of all users are mixed into a single file.
  * `true`: All users are recorded in a single file.
  * `false`: Each user is recorded separately.
* `streamUid`: String. User ID. The user whose audio or video is recorded in the file. In composite mode, `streamUid` is `0`.
* `trackType`: String. Type of the recorded file.
  * `"audio"`: Audio file.
  * `"video"`: Video file (no audio).
  * `"audio_and_video"`: Video file with audio.

For example, you start a recording session and get this event notification in which `startUtcMs` is the starting time of slice file 1. Then, the recording session continues and records slice file 2 to slice file N, during which the recording service does not send this notification again. If an error occurs when recording slice file N + 1, you lose this file and the recording session stops. Agora Cloud Recording automatically resumes the recording and triggers this event again. In the event notification, `startUtcMs` is the time when slice file N + 2 starts, and `discontinueUtcMs` is the time when slice file N stops.

#### recorder\_audio\_stream\_state\_changed [#recorder_audio_stream_state_changed]

eventType 43 indicates that the state of the audio stream has changed, and `details` includes the following fields:

* `msgName`: String. The message name, `recorder_audio_stream_state_changed`.
* `streamUid`: String. The ID of the user whose audio is being recorded. In composite recording mode, `streamUid` can be `0`, which represents the combined stream of all or the specified user IDs.
* `state` : Number. Whether Agora Cloud Recording is receiving the audio stream.
  * `0`: Agora Cloud Recording is receiving the audio stream.
  * `1`: Agora Cloud Recording is not receiving the audio stream.
* `UtcMs`: Number. The UTC time (ms) when the state of the audio stream changes.

#### recorder\_video\_stream\_state\_changed [#recorder_video_stream_state_changed]

`eventType` 44 indicates that the state of the video stream has changed, and `details` includes the following fields:

* `msgName`: String. The message name, `recorder_video_stream_state_changed`.
* `streamUid`: String. The ID of the user whose video is being recorded. In composite recording mode, `streamUid` can be `0`, which represents the combined stream of all or the specified user IDs.
* `state`: Number. Whether Agora Cloud Recording is receiving the video stream.
  * `0`: Agora Cloud Recording is receiving the video stream.
  * `1`: Agora Cloud Recording is not receiving the video stream.
* `UtcMs`: Number. The UTC time (ms) when the state of the video stream changes.

#### recorder\_snapshot\_file [#recorder_snapshot_file]

`eventType` 45 indicates that the screenshot is captured successfully and uploaded to the third-party cloud storage. The `details` includes the following fields:

* `msgName`: String. The message name, `"recorder_snapshot_file"`.
* `fileName`: String. The file name of the JPG file generated by the screenshot. The format is: `"fileName": "<fileNamePrefix>/<file name>"`. The `fileNamePrefix` refers to the path of the screenshot files in the third-party cloud storage.

#### vod\_started [#vod_started]

`eventType` 60 indicates that the uploader for ApsaraVideo for VoD has started and successfully acquired the upload credential. `details` includes the following fields:

* `msgName`: String. The message name, `"vod_started"`.
* `aliVodInfo`: JSON. The information of the video to upload.
  * `videoId`: String. The video ID.

#### vod\_triggered [#vod_triggered]

`eventType` 61 indicates that all recorded files have been uploaded to ApsaraVideo for VoD. `details` includes the following fields:

* `msgName`: String. The message name, `"vod_triggered"`.

#### web\_recorder\_started [#web_recorder_started]

`eventType` 70 indicates that web page recording starts. `details` includes the following field:

* `msgName`: String. The message name, `"web_recorder_started"`.
* `recorderStartTime`: Number. The time (UTC) when the recording starts. Unix timestamp in milliseconds.

#### web\_recorder\_stopped [#web_recorder_stopped]

`eventType` 71 indicates that web page recording stops. `details` includes the following field:

* `msgName`: String. The message name, `"web_recorder_stopped"`.
* `code`: Number. The error code. If the error code is not `0`, it means that the web page recording stopped abnormally.
* `message`: String. The error message, which indicates the reason why the recording stopped normally or abnormally.
* `details`: String. A specific description of the error message, which provides a more complete description of the reason why the recording stopped normally or abnormally. You can take corresponding measures based on the information.
* `fileList`: JSONArray. An array that contains detailed information of each recorded file.
  * `fileName`: String. The name of the M3U8 file or MP4 file.
  * `sliceStartTime`: Number. The Unix timestamp (ms) when the recording starts.

| Error Code | Message               | Description                                                                                                                                                                                                 |
| :--------- | :-------------------- | :---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `0`        | `ok`                  | Normal, the user stops the recording.                                                                                                                                                                       |
| `1`        | `max_recording_hour`  | The recording time reaches the set maximum recording length (`maxRecordingHour`), which causes the recording to stop.                                                                                       |
| `2`        | `capability_limit`    | The web page to be recorded uses an unsupported function, causing the web page recording to stop.                                                                                                           |
| `3`        | `start_engine_failed` | The recording engine failed to start, causing the web page recording to stop.                                                                                                                               |
| `4`        | `page_load_timeout`   | When you use the [web page load timeout detection](/en/realtime-media/cloud-recording/build/customize-the-recording/webpage-load-timeout), the web page load timeout causes the web page recording to stop. |
| `5`        | `access_url_failed`   | An error occurred when opening the web page to be recorded, which causes the web page recording to stop.                                                                                                    |
| `6`        | `recorder_error `     | The web page recording has an error and cannot continue, causing the web page recording to stop.                                                                                                            |

#### Example [#example-1]

```json
{
  "msgName": "web_recorder_stopped",
  "code": 1,
  "message": "max_recording_hour",
  "details": "max recording hour is 4",
  "fileList": [
    {
      "filename": "test_p1627613634_www.m3u8",
      "sliceStartTime": 1627613641393
    },
    {
      "filename": "test_p1627613634_www_0.mp4",
      "sliceStartTime": 1627613641393
    }
  ]
}
```

#### web\_recorder\_capability\_limit [#web_recorder_capability_limit]

`eventType` 72 indicates that the web page to record uses a feature that is unsupported by web page recording, and the recording service will stop immediately. `details` includes the following fields:

* `msgName`: String. The message name, `"web_recorder_capability_limit"`.
* `limitType`: String. The type of the feature that causes the failure.
  * `"resolution"`: The web page to record contains a video source with a resolution that exceeds 1280 × 720.
  * `"WebGL"`: The web page to record uses WebGL.
  * `"bandwidth"`：The upstream bandwidth exceeds 10 M, or the downstream bandwidth exceeds 1 G.

#### web\_recorder\_reload [#web_recorder_reload]

`eventType` 73 indicates a web page reload. `details` includes the following fields:

* `msgName`: String. The message name, `"web_recorder_reload"`.
* `reason`: String. The reason for the page reload:
  * `"audio_silence"`: Audio loss issue.
  * `"page_load_timeout"`: Web page load timeout. The callback returns this field only if the web page load detection function is enabled and there is a web page load timeout. See [Web page load detection](/en/realtime-media/cloud-recording/build/customize-the-recording/webpage-load-timeout).

#### transcoder\_started [#transcoder_started]

`eventType` 80 indicates that the transcoding starts, and `details` includes the following field:

`msgName`: String. The message name, `"transcoder_started"`.

#### transcoder\_completed [#transcoder_completed]

`eventType` 81 indicates that the transcoding completes, and `details` includes the following fields:

* `msgName`: String. The message name, `"transcoder_completed"`.
* `result`: String. The result of the transcoding.
  * `"all_success"`: Successfully transcodes all files.
  * `"partial_success"`: Fails to transcode some files.
  * `"fail"`: The transcoding fails.
* `uids`: JSONArray. The array contains the transcoding result and the name of the MP4 file of a specific user ID.
  * `uid`: String. The corresponding user ID of the MP4 file.
  * `result`: String. The result of transcoding.
    * `"success"`: Transcoding succeeds.
    * `"fail"`: Transcoding fails.
  * `fileList`: JSONArray. The array contains information about the MP4 file.
    * `fileName`: String. The name of the MP4 file.

#### download\_failed [#download_failed]

`eventType` 90 indicates that the recording service fails to download the recorded files. This callback is triggered only once in the entire recording process, and `details` includes the following fields:

* `msgName`: String. The message name, `"download_failed"`.
* `vendor`: Number. The name of the third-party cloud storage service, which is the same as `vendor` in your `start` request.
* `region`: Number. The region of the third-party cloud storage, which is the same as region set in your `start` request.
* `bucket`: String. The bucket name of the third-party cloud storage, which is the same as `bucket` set in your `start` request.
* `fileName`: String. The list of M3U8 or TS/WebM files that fail to be downloaded. The file names are separated by `";"`.

#### rtmp\_publish\_status [#rtmp_publish_status]

`eventType` 100 indicates the CDN streaming status of the web page recording. `details` includes the following fields:

* `msgName`：String. The message name, `"rtmp_publish_status"`.
* `rtmpUrl`: String. The CDN address you want to the push the stream to.
* `status`: Number. The CDN streaming status of the web page recording, including the following options:
  * `"connecting"`: Connecting to the CDN server.
  * `"publishing"`: Pushing the stream to the CDN server.
  * `"onhold"`: Pause streaming.
  * `"disconnected"`: Failed to connect to the CDN server. Agora recommends that you change the CDN address.

#### postpone\_transcode\_final\_result [#postpone_transcode_final_result]

`eventType` 1001 indicates the final transcoding result, and `details` includes the following fields:

* `msgName`: String. The message name, `"postpone_transcode_final_result"`.
* `result`: String. The final result of the transcoding.\\
  * `"total_success"`: Successfully transcodes all files.
  * `"partial_success"`: Fails to transcode some files.
  * `"failed"`: The transcoding fails.
* `fileList`: JSONArray. The array contains information about the generated MP4 files.
  * `fileName`: String. The name of an individual MP4 file.

#### Error codes related to uploading [#error-codes-related-to-uploading]

| Error code | Description                                                            |
| :--------- | :--------------------------------------------------------------------- |
| 32         | An error occurs in the configuration of the third-party cloud storage. |
| 47         | The recording file upload fails.                                       |
| 51         | An error occurs when the uploader processes the recorded files.        |

#### Warning codes related to uploading [#warning-codes-related-to-uploading]

| Warning code | Description                                                                     |
| :----------- | :------------------------------------------------------------------------------ |
| 31           | The recording service re-uploads the slice file to the specified cloud storage. |
| 32           | The recording service re-uploads the slice file to Agora Cloud Backup.          |

#### Error codes for the Agora Cloud Recording service [#error-codes-for-the-agora-cloud-recording-service]

| Error code | Description                                               |
| :--------- | :-------------------------------------------------------- |
| 50         | A timeout occurs in uploading the recorded files.         |
| 52         | A timeout occurs in starting the Cloud Recording Service. |

#### Status codes for the Agora Cloud Recording service [#status-codes-for-the-agora-cloud-recording-service]

| Status code | Description                                                                                                           |
| :---------- | :-------------------------------------------------------------------------------------------------------------------- |
| 0           | The recording has not started.                                                                                        |
| 1           | The initialization is complete.                                                                                       |
| 2           | The recorder is starting.                                                                                             |
| 3           | The uploader is ready.                                                                                                |
| 4           | The recorder is ready.                                                                                                |
| 5           | The first recorded file is uploaded. After uploading the first file, the status is `5` when the recording is running. |
| 6           | The recording stops.                                                                                                  |
| 7           | The Agora Cloud Recording service stops.                                                                              |
| 8           | The recording is ready to exit.                                                                                       |
| 20          | The recording exits abnormally.                                                                                       |

### IP address query API [#ip-address-query-api]

If your server that receives notification callbacks is behind a firewall, call the IP address query API to retrieve the IP addresses of Notifications and configure your firewall to trust all these IP addresses.

Agora occasionally adjusts the Notifications IP addresses. Best practice is to call this endpoint at least every 24 hours and automatically update the firewall configuration.

#### Prototype [#prototype]

* Method: `GET`
* Endpoint: `https://api.agora.io/v2/ncs/ip`

#### Request header [#request-header-1]

Authorization: You must generate a Base64-encoded credential with the Customer ID and Customer Secret provided by Agora, and then pass the credential to the Authorization field in the HTTP request header.

#### Request body [#request-body-1]

This API has no body parameters.

#### Response body [#response-body]

When the request succeeds, the response body looks like the following:

```json
{
    "data": {
        "service": {
            "hosts": [
                {
                    "primaryIP": "xxx.xxx.xxx.xxx"
                },
                {
                    "primaryIP": "xxx.xxx.xxx.xxx"
                }
            ]
        }
    }
}
```

Each primary IP field shows an IP address of Notifications server. When you receive a response, you need to note the primary IP fields and add all these IP addresses to your firewall's allowed IP list.

### Considerations [#considerations]

* Notifications does not guarantee that notification callbacks arrive at your server in the same order as events occur. Implement a strategy to handle messages arriving out of order.
* For improved reliability of Notifications, your server may receive more than one notification callback for a single event. Your server must be able to handle repeated messages.

<CalloutContainer type="info">
  <CalloutTitle>
    Tip
  </CalloutTitle>

  <CalloutDescription>
    To implement a strategy to ensure that you log only one callback event and ignore duplicate events, use a combination of the `noticeId` and `notifyMs` fields in the response body.
  </CalloutDescription>
</CalloutContainer>
