Hooks
Overview
Hooks let Grain notify your server when something happens. Create one with a hook_type
and a URL, and Grain sends a POST request to that URL whenever a matching event occurs.
Hook Types
| Type | Fires on | include support |
|---|---|---|
recording_added |
A recording was created | Recording Include |
recording_updated |
A recording was updated | Recording Include |
recording_deleted |
A recording was deleted | N/A |
highlight_added |
A highlight was created | Highlight Include |
highlight_updated |
A highlight was updated | Highlight Include |
highlight_deleted |
A highlight was deleted | N/A |
story_added |
A story was created | N/A |
story_updated |
A story was updated | N/A |
story_deleted |
A story was deleted | N/A |
upload_added |
An upload was created | Upload Include |
upload_updated |
An upload was updated | Upload Include |
The include field works the same as it does on List/Get endpoints, controlling what's
present on data in the delivered payload. Its shape depends on the hook_type's
resource (see above).
See Common Response Objects for the available fields.
Payload Examples
Every hook fires a POST to hook_url with the same envelope:
| Field | |
|---|---|
| type | The hook_type that fired |
| user_id | Id of the user the event belongs to |
| data | The resource itself, shaped by include (see Hook Types) |
Recording Added
{
"type": "recording_added",
"user_id": "eeee1111-ff22-gg33-hh44-iiii55555555",
"data": {
"id": "pppp6666-qq77-rr88-ss99-tttt00000000",
"title": "All Hands",
"source": "zoom",
"share_state": "public",
"url": "https://grain.com/share/recording/pppp6666-qq77-rr88-ss99-tttt00000000",
"media_type": "video",
"tags": [],
"start_datetime": "2026-01-01T09:30:00Z",
"teams": [
{
"id": "aaaa1111-bb22-cc33-dd44-eeee55555555",
"name": "My Team"
}
],
"workspace_shared": false,
"recorders": [
{
"id": "kkkk1111-ll22-mm33-nn44-oooo55555555",
"name": "Luke Skywalker",
"email": "luke@example.com"
}
],
"end_datetime": "2026-01-01T10:00:00Z",
"duration_ms": 1800000,
"thumbnail_url": "https://media.grain.com/public_thumbnails/recordings/pppp6666",
"meeting_type": {
"id": "ffff6666-gg77-hh88-ii99-jjjj00000000",
"name": "Project & Team Coordination",
"scope": "internal"
}
}
}Recording Added (with Include)
Same event, but the hook was created with include: {"participants": true}.
{
"type": "recording_added",
"user_id": "eeee1111-ff22-gg33-hh44-iiii55555555",
"data": {
"id": "pppp6666-qq77-rr88-ss99-tttt00000000",
"title": "All Hands",
"source": "zoom",
"share_state": "public",
"url": "https://grain.com/share/recording/pppp6666-qq77-rr88-ss99-tttt00000000",
"media_type": "video",
"tags": [],
"start_datetime": "2026-01-01T09:30:00Z",
"teams": [
{
"id": "aaaa1111-bb22-cc33-dd44-eeee55555555",
"name": "My Team"
}
],
"workspace_shared": false,
"recorders": [
{
"id": "kkkk1111-ll22-mm33-nn44-oooo55555555",
"name": "Luke Skywalker",
"email": "luke@example.com"
}
],
"participants": [
{
"id": "eeee1111-ff22-gg33-hh44-iiii55555555",
"name": "Luke Skywalker",
"scope": "internal",
"email": "luke@example.com",
"confirmed_attendee": true
},
{
"id": "jjjj6666-kk77-ll88-mm99-nnnn00000000",
"name": "Han Solo",
"scope": "internal",
"email": "solo@example.com",
"confirmed_attendee": false
}
],
"end_datetime": "2026-01-01T10:00:00Z",
"duration_ms": 1800000,
"thumbnail_url": "https://media.grain.com/public_thumbnails/recordings/pppp6666",
"meeting_type": {
"id": "ffff6666-gg77-hh88-ii99-jjjj00000000",
"name": "Project & Team Coordination",
"scope": "internal"
}
}
}Recording Deleted
_deleted hook types have no include support. data is always just the deleted resource's id.
{
"type": "recording_deleted",
"user_id": "eeee1111-ff22-gg33-hh44-iiii55555555",
"data": {
"id": "pppp6666-qq77-rr88-ss99-tttt00000000"
}
}