Skip to content

The minutes are ready

POST to your own endpoint meeting.ready

Protoki calls this address as soon as the minutes and tasks of a meeting exist. You set it up in the admin area under "Webhooks".

Verify the signature — this way and no other:

  1. Read Protoki-Signature, shaped t=<unix seconds>,v1=<hex>.
  2. If t is more than 300 seconds old, or in the future, throw the call away. Without this step a captured call can be replayed as often as you like.
  3. Take HMAC-SHA256 over the string <t>.<raw body> with your secret and compare it against v1 in constant time.

⚠️ The raw body is meant. Run it through JSON.parse and back through JSON.stringify and you change key order and whitespace — the check then fails every single time.

Answer with any 2xx, as fast as you can; the limit is 10 seconds. Anything else counts as a failure and is retried after 1 min, 5 min, 15 min, 1 h, 3 h, 6 h and 12 h — just over 22 hours in total. 410 Gone ends the attempts at once and for good.

Double delivery is possible, for instance when your 2xx is lost on the way. So deduplicate on delivery_id: it stays the same across every attempt and also sits in the Protoki-Delivery header.

Headers

NameTypeRequiredDescription
Protoki-SignaturestringyesTimestamp and signature: t=1770000000,v1=<hex>.
Protoki-EventstringyesValues: meeting.ready.
Protoki-DeliveryuuidyesDelivery id, for deduplication.

Body

Example

json
{
  "event": "meeting.ready",
  "delivery_id": "e07b6d13-84af-4c92-b5d7-0a9e1f83c246",
  "occurred_at": "2026-08-04T09:04:15Z",
  "workspace_id": "3b1f7c02-5d64-4c1a-9f88-2ac0e6b41d77",
  "data": {
    "meeting": {
      "id": "e07b6d13-84af-4c92-b5d7-0a9e1f83c246",
      "title": "Quarterly review",
      "platform": "string",
      "source": "string",
      "status": "string",
      "starts_at": "2026-08-04T08:00:00Z",
      "ends_at": "2026-08-04T09:00:00Z",
      "organizer_email": "anna@company.example",
      "join_url": "https://teams.microsoft.com/l/meetup-join/…"
    },
    "protocol": {
      "id": "e07b6d13-84af-4c92-b5d7-0a9e1f83c246",
      "tldr": "Quote goes out by Friday, engineering clarifies the interface.",
      "generated_at": "2026-08-04T09:04:12Z"
    },
    "action_items": [
      {
        "id": "c4d9f0a1-72be-4e35-8f10-6d2a5b8c9e04",
        "task": "Send the quote",
        "owner_name": "Anna Weber",
        "due": "2026-08-15",
        "verdict": "open",
        "status": "open"
      }
    ],
    "participants": [
      {
        "display_name": "Anna Weber",
        "email": "anna@company.example",
        "domain": "company.example",
        "is_internal": true,
        "attended": true
      }
    ]
  },
  "links": {
    "meeting": "https://protoki.com/api/v1/meetings/e07b6d13-84af-4c92-b5d7-0a9e1f83c246",
    "transcript": "https://protoki.com/api/v1/meetings/e07b6d13-84af-4c92-b5d7-0a9e1f83c246",
    "action_items": "https://protoki.com/api/v1/meetings/e07b6d13-84af-4c92-b5d7-0a9e1f83c246",
    "participants": "https://protoki.com/api/v1/meetings/e07b6d13-84af-4c92-b5d7-0a9e1f83c246",
    "app": "https://protoki.com/api/v1/meetings/e07b6d13-84af-4c92-b5d7-0a9e1f83c246"
  }
}
FieldTypeRequiredDescription
eventmeeting.readyyesWhich event this is. Branch on it, not on the shape of data.
delivery_iduuidyesStays the same across every retry — this is what you deduplicate double deliveries on.
occurred_atdate-timeyesWhen the event happened on our side, not when this attempt was sent.
workspace_iduuidyesThe workspace the meeting belongs to.
dataobjectyesThe payload of the event.
data.meetingobjectThe meeting this event is about.
data.meeting.iduuidIdentifies the meeting everywhere in this API.
data.meeting.titlestringSubject, as the calendar or the upload spelled it.
data.meeting.platformstringWhere the meeting took place.
data.meeting.sourcestringHow the meeting reached Protoki.
data.meeting.statusstringWhere the meeting stood when this call went out.
data.meeting.starts_atdate-time | nullStart of the appointment. null for an upload, which has none.
data.meeting.ends_atdate-time | nullEnd of the appointment. null for an upload.
data.meeting.organizer_emailstring | nullWho invited. null when the calendar gave nothing.
data.meeting.join_urlstring | nullThe link the recorder used to join. null for an upload.
data.protocolobject | nullThe short version and the id — the FULL text of the minutes is deliberately not in the body.
data.protocol.iduuidIdentifies this set of minutes.
data.protocol.tldrstring | nullThe short version. null when the model produced none.
data.protocol.generated_atdate-timeWhen the model wrote these minutes.
data.action_itemsobject[]The tasks taken from these minutes.
data.action_items[].iduuidyesIdentifies the task.
data.action_items[].taskstringyesWhat is to be done, in one sentence.
data.action_items[].owner_namestring | nullWho is to do it. null when nobody was named in the meeting.
data.action_items[].duedate | nullWhen it is due. null when no date was named.
data.action_items[].verdictstringyesOutcome of the second pass over the task. resolved_in_meeting means it was already dealt with while the meeting was still running. Values: open, resolved_in_meeting, discarded, duplicate, unclear_owner.
data.action_items[].statusstringyesWhere the task stands. This one is set by a person in the app, not by the model. Values: open, done, dismissed.
data.participantsobject[]Everyone the meeting knows about — present or only invited.
data.participants[].display_namestringThe name as the calendar or the meeting platform spelled it.
data.participants[].emailstring | nullEmail address. null when neither source gave one.
data.participants[].domainstring | nullEmail domain — this is what ties a meeting to a company.
data.participants[].is_internalbooleanThe email domain belongs to your own house.
data.participants[].attendedbooleanfalse means: invited, did not turn up. Somebody who sat through the meeting without saying a word counts as present.
linksobjectyesReady-made addresses for the follow-up call — the text of the minutes and the transcript are behind these.
links.meetinguriThe meeting in this API.
links.transcripturiThe transcript of this meeting.
links.action_itemsuriThe tasks of this meeting.
links.participantsuriThe participants of this meeting.
links.appuriThe meeting in the Protoki app, for a person to open.

Response 200

Accepted.

Other answers

CodeMeaning
410This receiver is gone — Protoki stops sending.

Protoki — EU/GDPR-compliant meeting notetaker.