Receive event notifications
A webhook is a user-defined callback over HTTP 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 Conversational AI Engine events.
The Agora message notification service offers low latency, high concurrency, stability, and reliability. In high-concurrency scenarios with conversational AI agents, the service maintains a real-time synchronized agent state machine at the business layer, enabling the following functions:
- Monitor agent join and leave events in real time.
- Monitor agent error events in real time.
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.
When an event you subscribe to occurs, the Agora business server sends the event message to the Agora message notification server, which then delivers the event notification to your server via an HTTPS POST request.
Prerequisites
Before you begin, make sure that you have:
- A valid Agora developer account and Agora Console project. See Enable Conversational AI for details .
- Servers that meet the following requirements:
- Support HTTPS protocol. To improve security, Agora message notification service no longer supports HTTP server addresses.
- Support HTTPS connection reuse (recommended), also known as keep-alive mode, to reduce message delivery delay. Agora recommends the following settings:
MaxKeepAliveRequests
: greater than or equal to 100.KeepAliveTimeout
: Greater than or equal to 10 seconds.
Enable notifications
To enable Notifications:
-
Log in to the Agora Console. Click Projects in the left navigation menu. Find the project for which you want to enable notifications and click ✎ to configure the project.
-
Under All Features, select Notifications. You see a list of Agora products for which notifications are available.
-
In the list of products, find Conversational AI Engine and expand the block to reveal configuration options.
-
Fill in the following information:
-
Event: Select all the events that you want to subscribe to. See Notification event types for details.
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.infoFor enhanced security, Notifications no longer supports
HTTP
addresses.-
To reduce the delay in notification delivery, best practice is to activate
HTTPS
persistent connection (also calledHTTP
keep-alive) on your server with the following settings:MaxKeepAliveRequests
: 100 or moreKeepAliveTimeout
: 10 seconds or more
-
-
Status: Set to enabled.
-
Whitelist: If your server is behind a firewall, check the box here, and ensure that you call the IP address query API to get the IP addresses of the Agora Notifications server and add them to the firewall's allowed IP list.
-
-
Copy the Secret by clicking the copy icon. You use this secret to Verify the Signature.
-
To test your configuration, press Check. Agora performs a health test for your configuration as follows:
-
The Notifications health test generates test events that correspond to your subscribed events, and then sends test event callbacks to your server.
In test event callbacks, the channelName is
test_webhook
, and the uid is12121212
. -
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. -
When the Notifications health test succeeds, press Apply Settings.
If the Notifications health test fails, follow the prompt on the Agora Console to troubleshoot the error.
-
Sample Code
Refer to the following sample code to handle incoming POST requests from the Notifications server.
Troubleshoot Common Errors
If the health check fails, follow the prompts in the Agora Console to troubleshoot.
Common issues include:
-
Request Timeout: Your server did not return a 200 response within 10 seconds.
- Ensure your server processes event callbacks promptly.
- If your response time is correct, contact Agora support to check network connectivity.
-
Certificate Error: Your HTTPS certificate is invalid.
- Verify that your SSL certificate is correct and has not expired.
- If your server is behind a firewall, ensure that all Agora event notification IPs are whitelisted.
-
Domain Name Unreachable: The domain name is invalid or cannot resolve to the correct IP address.
- Double-check your server deployment and DNS configuration.
-
Response Error: Your server returned an incorrect status code.
- Review the Agora Console for specific status codes and troubleshooting tips.
Once the health check passes, click Save Configuration in the Agora Console.
After approval, the Event Notification Service status will be marked as Enabled.
Receive Event Notification Callback
After you successfully activate Agora's event notification service, when a subscribed channel event occurs, the Agora event server sends an event notification callback to your server via an HTTPS POST
request. For details, see Conversational AI Engine Event Types.
Respond to Requests
After receiving an event notification callback, your server must respond to the Agora event server within 10 seconds. The response body must be in JSON format, but the body content is optional.
If the Agora event server does not receive a response within 10 seconds or the response status code is not 200
, the notification is considered failed. When a failure occurs, Agora immediately retries the notification. The retry interval increases progressively with each failure, and the retries stop after three attempts.
Verify the Signature
To enhance security, Agora provides a signature mechanism to verify that the request is from Agora.
When Agora sends an event notification callback, it generates a signature using HMAC/SHA1 and HMAC/SHA256 algorithms with a secret key. The signature values are included in the Agora-Signature
and Agora-Signature-V2
fields in the HTTPS request header.
Follow these steps to verify the signature:
-
Get the Key
When you Enable notifications, you receive a secret key.
-
Calculate the Signature
When you receive a callback, use the key and the parameters in the request body to compute the signature value using the HMAC/SHA1 or HMAC/SHA256 algorithm.
-
Compare the Signature
Compare your calculated signature with the corresponding field in the request header:
-
HMAC/SHA1: Compare the computed value with the
Agora-Signature
field. If they match, the request is from Agora. -
HMAC/SHA256: Compare the computed value with the
Agora-Signature-V2
field. If they match, the request is from Agora.
Agora provides sample code for signature verification in multiple languages for your reference.
-
- Python
- Javascript
- Java
- PHP
- Go
-
HMAC/SHA256
#!/usr/bin/env python2# !-*- coding: utf-8 -*-import hashlibimport hmac# Obtain the raw request body of the event notification and compute its signature. # In other words, the request_body in the following code is the binary byte array before deserialization, # not the dictionary after deserialization.request_body = '{"eventType":10,"noticeId":"4eb720f0-8da7-11e9-a43e-53f411c2761f","notifyMs":1560408533119,"payload":{"a":"1","b":2},"productId":1}'secret = 'secret'signature2 = hmac.new(secret, request_body, hashlib.sha256).hexdigest()# Print the computed signatureprint(signature2) # de96da5acf03b0021ac3b4fa2225e7ae6f3533a30d50bb02c08ea4fa748bda24
-
HMAC/SHA1
#!/usr/bin/env python2# !-*- coding: utf-8 -*-import hashlibimport hmac# Obtain the raw request body of the event notification and compute its signature. # In other words, the request_body in the following code is the binary byte array before deserialization, # not the dictionary after deserialization.request_body = '{"eventType":10,"noticeId":"4eb720f0-8da7-11e9-a43e-53f411c2761f","notifyMs":1560408533119,"payload":{"a":"1","b":2},"productId":1}'secret = 'secret'signature = hmac.new(secret, request_body, hashlib.sha1).hexdigest()# Print the computed signatureprint(signature) # 5a3bb6a6d9fad2ea9ae3fb707a14c9d7f3136df1
-
HMAC/SHA256
const crypto = require('crypto');// Obtain the raw request body of the event notification and compute its signature. // In other words, the requestBody in the following code is the binary byte array before deserialization, // not the object after deserialization.const requestBody = '{"eventType":10,"noticeId":"4eb720f0-8da7-11e9-a43e-53f411c2761f","notifyMs":1560408533119,"payload":{"a":"1","b":2},"productId":1}';const secret = 'secret';// Compute the HMAC signature using SHA-256const signature2 = crypto.createHmac('sha256', secret).update(requestBody, 'utf8').digest('hex');// Print the computed signatureconsole.log(signature2); // de96da5acf03b0021ac3b4fa2225e7ae6f3533a30d50bb02c08ea4fa748bda24
-
HMAC/SHA1
const crypto = require('crypto');// Obtain the raw request body of the event notification and compute its signature. // In other words, the requestBody in the following code is the binary byte array before deserialization, // not the object after deserialization.const requestBody = '{"eventType":10,"noticeId":"4eb720f0-8da7-11e9-a43e-53f411c2761f","notifyMs":1560408533119,"payload":{"a":"1","b":2},"productId":1}';const secret = 'secret';// Compute the HMAC signature using SHA-1const signature = crypto.createHmac('sha1', secret).update(requestBody, 'utf8').digest('hex');// Print the computed signatureconsole.log(signature); // 5a3bb6a6d9fad2ea9ae3fb707a14c9d7f3136df1
-
HMAC/SHA256
import javax.crypto.Mac;import javax.crypto.SecretKey;import javax.crypto.spec.SecretKeySpec;public class HmacSha { // Convert the encrypted byte array into a hexadecimal string public static String bytesToHex(byte[] bytes) { StringBuffer sb = new StringBuffer(); for (int i = 0; i < bytes.length; i++) { String hex = Integer.toHexString(bytes[i] & 0xFF); if (hex.length() < 2) { sb.append(0); } sb.append(hex); } return sb.toString(); } // HMAC/SHA256 encryption, returns the encrypted string public static String hmacSha256(String message, String secret) { try { SecretKeySpec signingKey = new SecretKeySpec(secret.getBytes("utf-8"), "HmacSHA256"); Mac mac = Mac.getInstance("HmacSHA256"); mac.init(signingKey); byte[] rawHmac = mac.doFinal(message.getBytes("utf-8")); return bytesToHex(rawHmac); } catch (Exception e) { throw new RuntimeException(e); } } public static void main(String[] args) { // Obtain the raw request body of the event notification and compute its signature. // In other words, the request_body in the following code is the binary byte array before deserialization, // not the object after deserialization. String request_body = "{\"eventType\":10,\"noticeId\":\"4eb720f0-8da7-11e9-a43e-53f411c2761f\",\"notifyMs\":1560408533119,\"payload\":{\"a\":\"1\",\"b\":2},\"productId\":1}"; String secret = "secret"; // Print the computed HMAC/SHA256 signature System.out.println(hmacSha256(request_body, secret)); // Expected output: de96da5acf03b0021ac3b4fa2225e7ae6f3533a30d50bb02c08ea4fa748bda24 }}
-
HMAC/SHA1
import javax.crypto.Mac;import javax.crypto.SecretKey;import javax.crypto.spec.SecretKeySpec;public class HmacSha { // Convert the encrypted byte array into a hexadecimal string public static String bytesToHex(byte[] bytes) { StringBuffer sb = new StringBuffer(); for (int i = 0; i < bytes.length; i++) { String hex = Integer.toHexString(bytes[i] & 0xFF); if (hex.length() < 2) { sb.append(0); } sb.append(hex); } return sb.toString(); } // HMAC/SHA1 encryption, returns the encrypted string public static String hmacSha1(String message, String secret) { try { SecretKeySpec signingKey = new SecretKeySpec(secret.getBytes("utf-8"), "HmacSHA1"); Mac mac = Mac.getInstance("HmacSHA1"); mac.init(signingKey); byte[] rawHmac = mac.doFinal(message.getBytes("utf-8")); return bytesToHex(rawHmac); } catch (Exception e) { throw new RuntimeException(e); } } public static void main(String[] args) { // Obtain the raw request body of the event notification and compute its signature. // In other words, the request_body in the following code is the binary byte array before deserialization, // not the object after deserialization. String request_body = "{\"eventType\":10,\"noticeId\":\"4eb720f0-8da7-11e9-a43e-53f411c2761f\",\"notifyMs\":1560408533119,\"payload\":{\"a\":\"1\",\"b\":2},\"productId\":1}"; String secret = "secret"; // Print the computed HMAC/SHA1 signature System.out.println(hmacSha1(request_body, secret)); // Expected output: 5a3bb6a6d9fad2ea9ae3fb707a14c9d7f3136df1 }}
-
HMAC/SHA256
<?phpfunction assertEqual($expect, $actual){ if ($expect != $actual) { echo("\n assert failed"); echo("\n expect:\n " . $expect); echo("\n actual:\n " . $actual); echo("\n"); } else { echo("assert ok\n"); echo("\n"); }}// Obtain the raw request body from the notification and compute its signature.// In other words, the requestBody in the following code is the binary byte array before deserialization, // not the object after deserialization.$request_body = '{"eventType":10,"noticeId":"4eb720f0-8da7-11e9-a43e-53f411c2761f","notifyMs":1560408533119,"payload":{"a":"1","b":2},"productId":1}';$secret = 'secret';// The value of Agora-Signature-V2 in the request header$sha256 = 'de96da5acf03b0021ac3b4fa2225e7ae6f3533a30d50bb02c08ea4fa748bda24';$res2 = (hash_hmac('sha256', $request_body, $secret));assertEqual($res2, $sha256);?>
-
HMAC/SHA1
<?phpfunction assertEqual($expect, $actual){ if ($expect != $actual) { echo("\n assert failed"); echo("\n expect:\n " . $expect); echo("\n actual:\n " . $actual); echo("\n"); } else { echo("assert ok\n"); echo("\n"); }}// Obtain the raw request body from the notification and compute its signature.// In other words, the requestBody in the following code is the binary byte array before deserialization, // not the object after deserialization.$request_body = '{"eventType":10,"noticeId":"4eb720f0-8da7-11e9-a43e-53f411c2761f","notifyMs":1560408533119,"payload":{"a":"1","b":2},"productId":1}';$secret = 'secret';// The value of Agora-Signature in the request header$sha1 = '5a3bb6a6d9fad2ea9ae3fb707a14c9d7f3136df1';$res1 = (hash_hmac('sha1', $request_body, $secret));assertEqual($res1, $sha1);?>
-
HMAC/SHA256
package mainimport ( "crypto/hmac" "crypto/sha256" "encoding/hex" "fmt")func main() { // Obtain the raw request body of the message notification and compute its signature. // In other words, the `request_body` in the code below is the binary byte array before deserialization, // not the dictionary after deserialization. request_body := `{"eventType":10,"noticeId":"4eb720f0-8da7-11e9-a43e-53f411c2761f","notifyMs":1560408533119,"payload":{"a":"1","b":2},"productId":1}` secret := "secret" fmt.Println(calcSignatureV2(secret, request_body)) // de96da5acf03b0021ac3b4fa2225e7ae6f3533a30d50bb02c08ea4fa748bda24}func calcSignatureV2(secret, payload string) string { mac := hmac.New(sha256.New, []byte(secret)) mac.Write([]byte(payload)) return hex.EncodeToString(mac.Sum(nil))}
-
HMAC/SHA1
package mainimport ( "crypto/hmac" "crypto/sha1" "encoding/hex" "fmt")func main() { // Obtain the raw request body of the message notification and compute its signature. // In other words, the `request_body` in the code below is the binary byte array before deserialization, // not the dictionary after deserialization. request_body := `{"eventType":10,"noticeId":"4eb720f0-8da7-11e9-a43e-53f411c2761f","notifyMs":1560408533119,"payload":{"a":"1","b":2},"productId":1}` secret := "secret" fmt.Println(calcSignatureV1(secret, request_body)) // 5a3bb6a6d9fad2ea9ae3fb707a14c9d7f3136df1}func calcSignatureV1(secret, payload string) string { mac := hmac.New(sha1.New, []byte(secret)) mac.Write([]byte(payload)) return hex.EncodeToString(mac.Sum(nil))}