- Automated webhooks vs manual webhooks
- Steps to set up webhooks
- Verifying Webhook Requests
- Schema
- FAQs
- Related articles
With webhooks, Read users can push meeting notes and report data to a custom endpoint so other tools or services can automatically receive meeting updates. When a report becomes available, Read sends a webhook request containing the meeting data to the configured endpoint. If needed, users can also manually trigger a webhook from an individual meeting report.
Prerequisites
- Webhooks are only available on the Pro, Enterprise, and Enterprise+ plans.
- Users must verify their email address before creating webhooks.
- There is a maximum of 15 webhooks per user; older webhooks must be deleted to create new ones once you have reached this limit.
Automated webhooks vs manual webhooks
Automated webhooks are triggered when the meeting report is successfully generated after a meeting ends. You must have at least "Viewer" access to the report at the time of the meeting, so it does not work if your access was given after the meeting. It generally only gets triggered once per report, except it will automatically retry several times with exponential backoff whenever it receives a non-2xx HTTP status code from your configured endpoint. After 25 tries, if it still fails, it will stop trying and the webhook will change to a "Stopped" status (Refer "Reasons why my webhook stoped" below).
Manual webhooks are triggered by a user from a report page. It also works if your access to the report was given after the meeting, but it must be at least "Editor" level access. As long as you have at least one webhook configured, you can click on the webhooks button from the distribution menu and a pop-up will appear that allows you to choose which webhook you want to push the report to. Once it is pushed successfully, you'll see a confirmation message.
Steps to set up webhooks
1. Navigate to the Integrations page
2. In the Webhooks section, click "Add webhook"
3. Enter a webhook name and URL
Please do not use Unicode characters or UTF-8 emojis in the name, and make sure to provide an HTTPS URL. The application at this URL should be configured to receive a JSON payload with our standard schema.
4. (Optional) Copy your signing key
As a security best practice, we generate a unique signing key for each webhook you create. You can use this signing key later to verify that calls received by your custom endpoint are actually coming from Read AI (more details below).
5. Test your webhook
We recommended testing at this point so you can catch issues early, before you're relying on the automation to be working. You can use the "Send test request" option here to trigger a request with dummy data, or if you want to try with real meeting data, you can go to an old report and trigger the webhook manually.
If you encounter any issues, make sure your endpoint is live and properly configured to return a 2xx HTTP status code when it receives a JSON payload with our standard schema.
6. Have a meeting and see the data flow
Please note that the status will still show as "Pending" until you have a meeting after setting up the webhook. As long as the URL you provided responds successfully to the webhook when triggered, the status will then update to "Active".
Verifying Webhook Requests
To help ensure that webhook requests you receive are actually sent by Read AI, each webhook delivery includes a cryptographic signature. You can use the signing key provided in your webhook configuration to verify that the request originated from Read AI and was not modified in transit. Note that this is only available for new webhooks created after March 17, 2026.
How It Works
When Read AI sends a webhook, we compute a signature using your webhook’s signing key and the raw request body. The resulting signature is included in the request header X-Read-Signature.
On your server, you can independently compute the same signature using the signing key. If your computed value matches the signature included in the webhook request, the payload is authentic.
Steps to Verify a Webhook
-
Retrieve your webhook signing key
In the Read AI webhook settings, you will see a signing key associated with your webhook endpoint. Store this key securely and use it only on your server when verifying webhook requests. -
Read the raw request body
Capture the exact raw request payload sent to your endpoint. Do not modify, reformat, or parse it before verification, since the signature is calculated using the original raw body. -
Extract the signature header
Read the value of theX-Read-Signatureheader from the incoming request. This header contains the HMAC signature generated by Read AI. -
Compute your own signature
Using your webhook signing key, compute an HMAC SHA‑256 hash of the raw request body. -
Compare the signatures
Compare the signature you computed with the value from theX-Read-Signatureheader. If they match, the request was sent by Read AI and the payload has not been tampered with.
Note: Webhooks also contain a unique request_id property in the body that can be used to detect and ignore duplicate payloads, preventing replay attacks.
Verification Sample Code
Python
import base64
import hmac
import hashlib
# Replace with the body of the request
body = b'{...}'
# Replace with the value of the X-Read-Signature header received on each request
header_sig = "d2f733f07d3862f707e761be1b72c6533133977307abf272dabb66addfbec9a1"
# ⚠️ IMPORTANT: Store the signing key in a secure location, not in code. This is for illustrative purposes only.
signing_key = "GHx4YT/StkcArjYPypjFG48FbZvgzBuDBOz5pCecbro="
key_bytes = base64.b64decode(signing_key)
digest = hmac.new(key_bytes, body, hashlib.sha256).hexdigest()
if hmac.compare_digest(digest, header_sig.lower()):
print("✅ Signature verified. This request came from Read AI!")
else:
print("❌ Signature verification failed!")
JavaScript
const crypto = require('crypto');
// Replace with the body of the request
const body = '{...}';
// Replace with the value of the X-Read-Signature header received on each request
const headerSig = "d2f733f07d3862f707e761be1b72c6533133977307abf272dabb66addfbec9a1";
// ⚠️ IMPORTANT: Store the signing key in a secure location, not in code. This is for illustrative purposes only.
const signingKey = "GHx4YT/StkcArjYPypjFG48FbZvgzBuDBOz5pCecbro=";
const keyBytes = Buffer.from(signingKey, 'base64');
const digest = crypto.createHmac('sha256', keyBytes)
.update(body, 'utf8')
.digest('hex');
if (crypto.timingSafeEqual(Buffer.from(digest), Buffer.from(headerSig.toLowerCase()))) {
console.log("✅ Signature verified. This request came from Read AI!");
} else {
console.log("❌ Signature verification failed!");
}Go
package main
import (
"crypto/hmac"
"crypto/sha256"
"encoding/base64"
"encoding/hex"
"fmt"
)
func main() {
// Replace with the body of the request
body := []byte("{...}")
// Replace with the value of the X-Read-Signature header received on each request
headerSig := "d2f733f07d3862f707e761be1b72c6533133977307abf272dabb66addfbec9a1"
// ⚠️ IMPORTANT: Store the signing key in a secure location, not in code. This is for illustrative purposes only.
signingKey := "GHx4YT/StkcArjYPypjFG48FbZvgzBuDBOz5pCecbro="
keyBytes, err := base64.StdEncoding.DecodeString(signingKey)
if err != nil {
panic(err)
}
h := hmac.New(sha256.New, keyBytes)
h.Write(body)
digest := hex.EncodeToString(h.Sum(nil))
if hmac.Equal([]byte(digest), []byte(headerSig)) {
fmt.Println("✅ Signature verified. This request came from Read AI!")
} else {
fmt.Println("❌ Signature verification failed!")
}
}
Ruby
require 'openssl'
require 'base64'
# Replace with the body of the request
body = '{...}'
# Replace with the value of the X-Read-Signature header received on each request
header_sig = "d2f733f07d3862f707e761be1b72c6533133977307abf272dabb66addfbec9a1"
# ⚠️ IMPORTANT: Store the signing key in a secure location, not in code. This is for illustrative purposes only.
signing_key = "GHx4YT/StkcArjYPypjFG48FbZvgzBuDBOz5pCecbro="
key_bytes = Base64.decode64(signing_key)
digest = OpenSSL::HMAC.hexdigest('SHA256', key_bytes, body)
if OpenSSL.secure_compare(digest, header_sig.downcase)
puts "✅ Signature verified. This request came from Read AI!"
else
puts "❌ Signature verification failed!"
end
Best Practices
- Always verify webhook signatures before processing the payload.
- Reject requests with missing or invalid
X-Read-Signaturesignatures. - Store your signing key securely and never expose it in client-side code.
- Use a timing-safe comparison function when comparing signatures to avoid timing attacks.
Schema
JSON Properties
Whenever a webhook triggers, Read will send an HTTP POST request with a raw JSON payload in the body, containing the following top-level properties:
-
session_id: A unique identifier for the meeting session -
trigger: The event that triggered the webhooks (either "meeting_end" or "manual") -
title: The title of the meeting -
start_time: The start time of the meeting in UTC format -
end_time: The end time of the meeting in UTC format -
participants: An array of participants in the meeting -
owner: The meeting owner -
summary: The high-level meeting summary -
action_items: An array of action items discussed during the meeting -
key_questions: An array of key questions raised during the meeting -
topics: An array of topics covered in the meeting -
report_url: A URL to the meeting report in Read's web UI -
chapter_summaries: More detailed summaries for the meeting, broken up into sequential chapters, along with their associated topics. -
transcript: The full meeting transcript, including speaker names and timestamps in Unix time (milliseconds). -
platform_meeting_id: The unique ID for this meeting from e.g. Teams, Zoom, or Meet -
platform: The platform that the meeting was recorded from (note that recordings from the Read AI desktop or mobile app will always list that as the platform) -
request_id: Unique ID generated for each request, which can be used to ignore duplicate payloads, preventing replay attacks.
Example Payload
Here's an example payload, which should look very similar to the payload sent in a webhook test request.
{
"session_id": "SESSIONID",
"trigger": "meeting_end",
"title": "Meeting Title",
"start_time": "2026-01-01T00:00:00Z",
"end_time": "2026-01-01T01:00:00Z",
"participants": [
{
"name": "Participant One",
"first_name": "Participant",
"last_name": "One",
"email": "participant_one@company.com"
},
{
"name": "Participant Two",
"first_name": "Participant",
"last_name": "Two",
"email": null
}
],
"owner": {
"name": "Participant One",
"first_name": "Participant",
"last_name": "One",
"email": "participant_one@company.com"
},
"summary": "Meeting summary goes here...",
"action_items": [
{
"text": "Action item one"
},
{
"text": "Action item two"
}
],
"key_questions": [
{
"text": "Key question one?"
},
{
"text": "Key question two?"
}
],
"topics": [
{
"text": "Topic one"
},
{
"text": "Topic two"
}
],
"report_url": "https://app.read.ai/analytics/meetings/SESSIONID",
"chapter_summaries": [
{
"title": "Chapter one",
"description": "First chapter description",
"topics": [
{
"text": "Topic one"
}
]
},
{
"title": "Chapter two",
"description": "Second chapter description",
"topics": [
{
"text": "Topic two"
}
]
}
],
"transcript": {
"speaker_blocks": [
{
"start_time": "1719514000000",
"end_time": "1719514001000",
"speaker": {
"name": "Speaker 1"
},
"words": "Good morning everyone!"
},
{
"start_time": "1719514002000",
"end_time": "1719514003000",
"speaker": {
"name": "Speaker 2"
},
"words": "Hi!"
}
],
"speakers": [
{
"name": "Speaker 1"
},
{
"name": "Speaker 2"
}
]
}
"platform_meeting_id": "abc-defg-hij",
"platform": "meet",
"request_id": "01KKVQZ19X57NMNFTS8YXJNZE4"
}
FAQs
Why don't I receive webhooks for some of my meetings?
Webhooks are only triggered automatically when a report is first created, and only for reports that you had direct access to at that time. It does not trigger when reports are shared with you later, or if you only got access using Team Report Access or Global Report Access (which are considered indirect access).
Can webhooks push reports generated from file uploads?
No, webhooks (both auto and manual) are designed for standard meeting reports; reports generated from file uploads do not trigger them.
Can I get access to all of my team's or workspace's reports with webhooks?
There is currently no workspace or team-level version of webhooks, each user can only send their own meeting reports via webhooks. Additionally, webhooks will not trigger for reports that you only have access to because of Team Report Access or Global Report Access.
Do webhooks automatically retry on failure?
Yes, Read will automatically retry a webhook request whenever we receive any HTTP status code above 299 from your endpoint. Each request will retry up to 5 times (for a total of 6 attempts).
Why does my webhook not include a signature?
Signatures were added to Read’s webhooks in March 2026, and webhooks generated prior to this will not include signatures. In order to get signatures for and verify these older webhooks, you will need to delete and re-create them.
Why does my webhook show as "stopped"?
If Read tries and fails to send data to a webhook, after 25 consecutive failures our system will automatically disable the webhook and mark it as "stopped".
Since webhooks are only available for paid users, if your subscription was ended for any reason, your webhooks would stop as well.
If this happens to you, you may need to delete and re-create it in order to enable it again.
Related articles
- For a no-code solution to connect to other platforms using the same approach of automatic triggers, check out Getting Started with Zapier.
- For on-demand access to meeting data via other platforms or your own custom applications, check out the MCP Server and REST API.