- User webhooks vs. workspace webhooks
- Automated webhooks vs manual webhooks
- Steps to set up webhooks
- Verifying Webhook Requests
- Schema
-
FAQs
- Why don't I receive webhooks for some of my meetings?
- Can webhooks push reports generated from file uploads?
- Can I get access to all of my team's or workspace's reports with webhooks?
- Do webhooks automatically retry on failure?
- Why does my webhook not include a signature?
- Why does my webhook show as "stopped"?
- Related articles
With webhooks, Read 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.
There are two kinds of webhooks:
- User webhooks push the meeting reports for a single user (the person who created the webhook). Any user can set these up for their own meetings.
- Workspace webhooks push the meeting reports for every user in the workspace to a single endpoint. These are created and managed by workspace admins and owners.
If needed, a user 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 20 webhooks per user and a separate maximum of 20 workspace webhooks; older webhooks must be deleted to create new ones once a limit is reached.
- Only workspace admins and owners can create or edit workspace webhooks.
User webhooks vs. workspace webhooks
| User webhooks | Workspace webhooks | |
|---|---|---|
| Who can create/edit | Any user | Workspace admins and owners only |
| Where to set up | Integrations → Your Integrations tab → Webhooks | Integrations → Workspace tab → Webhooks |
| Reports pushed | Only the creating user's reports | Reports for all members of the workspace |
trigger options
|
meeting_end |
meeting_start, meeting_end
|
| Manual push from a report | Available to the creating user | Available to all workspace members |
| Payload schema | Standard schema | Same standard schema |
| Limit | 20 per user | 20 per workspace (separate cap) |
Both webhook types share the same payload schema and the same signature-verification process. The main differences are who can configure them and whose reports they push.
Webhooks fire only for reports that a member had direct access to at the time the report was created. They do not fire for reports a member only sees through Team Report Access or Global Report Access (indirect access).
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
Setting up a workspace webhook? Workspace admins and owners manage these on the Workspace tab of the Integrations page instead. The remaining steps (name, URL, signing key, test, go live) are identical.
2. In the Webhooks section, click "Create webhook"
If creating a workspace webhook, you will also be prompted to acknowledge and confirm understanding of the risks before proceeding.
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.
If creating a workspace webhook, you'll also have the option to trigger when a meeting starts, and you must select at least one of the trigger options.
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. (Optional) Test your webhook
We recommended testing at this point so you can catch issues early. 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 send your first real payload for a meeting. 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 webhook —meeting_end, ormeeting_start(workspace webhooks only). Note that manually triggering a webhook from a report always sendsmeeting_end. -
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.
"Meeting start" payloads
Workspace webhooks can also fire at the start of a meeting. Because no report exists yet, a meeting_start payload contains only the following subset of the standard schema:
session_id-
trigger("meeting_start") titlestart_timeownerplatformplatform_meeting_idrequest_id
All other fields (such as end_time, summary, action_items, topics, chapter_summaries, transcript, and report_url) are not present at meeting start. meeting_start deliveries are signed with the same X-Read-Signature header, so verify them exactly as you would a meeting_end payload.
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?
Yes. Workspace webhooks push the meeting reports for every member of the workspace to a single endpoint. They can only be created and edited by workspace admins and owners, from the Workspace tab of the Integrations page.
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.