Official documentation for authentication, webhook management, and signed recording event delivery.
Quick start · Endpoints · Authentication · Signatures
| Contract | Current behavior |
|---|---|
| Base URL | https://api.memoket.ai |
| Access | OAuth 2.1 + PKCE, or a personal API token |
| Scope | One account per token; recording data is read-only |
| Delivery | Signed HTTPS POST; expect possible duplicates |
Requires a Memoket account, a public HTTPS endpoint, and an access token.
curl --request POST https://api.memoket.ai/v1/webhooks \
--header "Authorization: Bearer $MEMOKET_ACCESS_TOKEN" \
--header "Content-Type: application/json" \
--data '{
"url": "https://your-app.example.com/hooks/memoket",
"events": ["summary.completed"]
}'The response contains a webhook secret. Store it immediately: it is shown
only once.
Verify X-Memoket-Signature against the unchanged request bytes before parsing
the payload. See Verifying signatures.
Return 2xx within 5 seconds and de-duplicate on the event id. Use the
delivery log to reconcile failures.
| Purpose | Endpoint |
|---|---|
| OAuth discovery and authorization | GET /.well-known/oauth-authorization-server, POST /oauth/register, GET /oauth/authorize, POST /oauth/token |
| Create a subscription | POST /v1/webhooks |
| List subscriptions | GET /v1/webhooks |
| Delete a subscription | DELETE /v1/webhooks/{endpoint_id} |
| Get sample events | GET /v1/webhook-events/samples |
| Update a subscription | PATCH /v1/webhooks/{endpoint_id} |
| Send a test event | POST /v1/webhooks/{endpoint_id}/test |
| List recent deliveries | GET /v1/webhooks/{endpoint_id}/deliveries |
| Receive an event | Memoket → your HTTPS endpoint (signed POST) |
Protected requests use the Bearer token for one Memoket account.
Authorization: Bearer <access_token>
Use OAuth 2.1 for platform integrations and a personal token for direct server-to-server integrations.
Use the live discovery document as the authoritative endpoint list:
GET https://api.memoket.ai/.well-known/oauth-authorization-server
PKCE (S256) is required. Production HTTPS redirect URIs must be allow-listed
by Memoket; loopback HTTP addresses (127.0.0.1, localhost, or ::1) do not.
OAuth request and response examples
Discovery metadata
{
"issuer": "https://api.memoket.ai",
"authorization_endpoint": "https://api.memoket.ai/oauth/authorize",
"token_endpoint": "https://api.memoket.ai/oauth/token",
"registration_endpoint": "https://api.memoket.ai/oauth/register",
"scopes_supported": ["mcp:connect"],
"response_types_supported": ["code"],
"grant_types_supported": ["authorization_code", "refresh_token"],
"code_challenge_methods_supported": ["S256"],
"token_endpoint_auth_methods_supported": ["client_secret_basic", "client_secret_post", "none"]
}Register a client
POST /oauth/register
Content-Type: application/json
{
"redirect_uris": ["http://127.0.0.1:8787/callback"],
"client_name": "Your App",
"token_endpoint_auth_method": "none",
"grant_types": ["authorization_code", "refresh_token"],
"response_types": ["code"],
"scope": "mcp:connect"
}
Public clients use "token_endpoint_auth_method": "none" and receive no
client_secret. Confidential clients receive a secret and must store it
securely. They use HTTP Basic for client_secret_basic or a client_secret
form field for client_secret_post.
Authorize
GET /oauth/authorize
?response_type=code
&client_id=<client_id>
&redirect_uri=<redirect_uri>
&scope=mcp:connect
&state=<random>
&code_challenge=<pkce_challenge>
&code_challenge_method=S256
Exchange the code:
POST /oauth/token
Content-Type: application/x-www-form-urlencoded
grant_type=authorization_code
&code=<code>
&redirect_uri=<redirect_uri>
&client_id=<client_id>
&code_verifier=<pkce_verifier>
{
"access_token": "…",
"token_type": "Bearer",
"expires_in": 3600,
"refresh_token": "…",
"scope": "mcp:connect",
"email": "user@example.com"
}access_tokenis valid for 1 hour.refresh_tokenis valid for 90 days. Refresh withgrant_type=refresh_token&refresh_token=…&client_id=…and the registered client authentication method.emailmay be empty. Do not use it for authorization.
Personal tokens use the same Bearer header:
memoket login
memoket token create --expires-in 90The current release allows up to 365 days and invalidates tokens after about 90 days of inactivity.
All require Authorization: Bearer <token>; all responses are JSON.
POST /v1/webhooks
Content-Type: application/json
{ "url": "https://your-app.example.com/hooks/memoket", "events": ["summary.completed"] }
| Field | Type | Notes |
|---|---|---|
url |
string | Required. Must be HTTPS and publicly resolvable. |
events |
string[] | Event types to deliver. An empty or omitted list receives nothing. |
The HMAC secret is returned only once.
Example response
{
"endpoint_id": "2079452035110989824",
"secret": "whsec_3237094abd8b…",
"url": "https://your-app.example.com/hooks/memoket",
"events": ["summary.completed"],
"status": 1
}endpoint_id identifies the subscription.
GET /v1/webhooks
Example response and status fields
{
"items": [
{
"id": "2079452035110989824",
"url": "https://your-app.example.com/hooks/memoket",
"events": ["summary.completed"],
"status": 1,
"consecutive_failures": 0,
"created_at": "2026-07-21T06:22:54Z",
"updated_at": "2026-07-21T06:22:54Z"
}
]
}status is 1 when active and 2 when disabled. consecutive_failures
counts failed attempts, including retries, and resets after a success.
updated_at advances on every delivery attempt.
DELETE /v1/webhooks/{endpoint_id}
200: { "ok": true }
Preview recent or synthetic payloads without waiting for a live event.
GET /v1/webhook-events/samples?event=summary.completed
Returns a top-level array: recent payloads when available, otherwise one
synthetic evt_sample_… payload. The payload reference
is authoritative. limit defaults to 3 and is capped at 10.
Warning
The current release treats an omitted events field as an empty list.
Always send the subscription's existing events, including for URL-only
updates, and verify the event list after updating.
PATCH /v1/webhooks/{endpoint_id}
Content-Type: application/json
{ "url": "https://your-app.example.com/hooks/new", "events": ["summary.completed"] }
200: { "item": { …updated subscription, same shape as list items… } }
Omit url or send an empty string to keep it. Send "events": [] only to
clear this endpoint's event list. Changing the URL of a disabled endpoint
re-enables it and resets its failure count; changing only events does not.
POST /v1/webhooks/{endpoint_id}/test
200: { "event_id": "evt_01KY7H7FYHWMGM5FYE5DG4MPNC", "queued": true }
Queues a signed webhook.ping. queued: true confirms queue acceptance, not
HTTP delivery. Check the delivery log when an item is created; endpoints
disabled or deleted before dispatch may be skipped without one.
GET /v1/webhooks/{endpoint_id}/deliveries?page=1&page_size=20
Example response and delivery fields
{
"items": [
{
"id": "2080277851302588416",
"endpoint_id": "2080277840846188544",
"event_id": "evt_01KY7H7FYHWMGM5FYE5DG4MPNC",
"event_type": "webhook.ping",
"status": 0,
"attempts": 1,
"last_error": "http_405",
"delivered_at": "",
"created_at": "2026-07-23T13:04:24Z"
}
]
}status is 0 pending, 1 delivered, or 2 terminal failure. attempts
counts all processing attempts, including failures before an HTTP request.
last_error is the latest failure reason; delivered_at is empty until
delivery succeeds.
page defaults to 1. page_size defaults to 20 and is capped at 100.
| Event | When it fires |
|---|---|
summary.completed |
A summary record is ready. Short recordings may contain a placeholder note. |
webhook.ping |
Test event, sent only via POST /v1/webhooks/{endpoint_id}/test. |
Memoket sends an HTTP POST to your subscribed url.
| Header | Description |
|---|---|
Content-Type |
application/json |
X-Memoket-Event |
Event type, e.g. summary.completed |
X-Memoket-Delivery |
Unique delivery id |
X-Memoket-Timestamp |
Unix timestamp (seconds) used in the signature |
X-Memoket-Signature |
sha256=<hex HMAC> (Section 5) |
Example payload
{
"id": "evt_01KY1S5CDZ44DRPMX49DYHCKX8",
"type": "summary.completed",
"created_at": "2026-07-21T07:27:36Z",
"data": {
"audio_id": "2079468251535941632",
"status": 1,
"title": "Weekly sync: roadmap and priorities",
"duration_ms": 126400,
"recorded_at": "2026-07-21T07:26:34Z",
"transcript": { "language": "en", "text": "Full transcript…", "truncated": false, "segments": [] },
"summaries": [
{ "label": "brief", "template_name": "Brief", "format": "markdown", "content": "#### Summary\n\n- …" }
]
}
}Envelope (always present)
| Field | Type | Description |
|---|---|---|
id |
string | Unique event id (de-duplicate on this). |
type |
string | Event type. |
created_at |
string (ISO 8601) | When the event was generated. |
data |
object | Event-specific payload. |
summary.completed data: only audio_id is guaranteed. Treat every other
field as optional.
| Field | Type | Description |
|---|---|---|
audio_id |
string | Recording id. Always present. |
report_id |
string, optional | Summary report id (absent for auto-analyzed recordings). |
status |
integer, optional | Processing status. |
title |
string, optional | Recording title. |
duration_ms |
integer, optional | Length in milliseconds. |
recorded_at |
string (ISO 8601), optional | When recorded. |
transcript |
object, optional | { language?, text, truncated, segments? }; segment times are seconds. |
summaries |
array, optional | { label, content, format, template_name? }; consumers must accept markdown / html and tolerate other values. |
Payload size and format edge cases
Transcript segments use { speaker, start, end, text }, with times in seconds.
Memoket targets about 1 MiB by removing segments first, then truncating transcript
text on a UTF-8 boundary and setting truncated: true. Summaries are not
truncated, so payloads can exceed that target. HTML is normally converted to
Markdown; conversion failures and unknown formats pass through unchanged.
Historical samples can contain HTML. Transcript and summary enrichment fail
independently, so either field may be omitted.
signature = "sha256=" + HMAC_SHA256(secret, timestamp + "." + raw_request_body)
Use the timestamp header and exact request bytes. Do not re-serialize the body. Compare signatures in constant time, checking length first.
Node.js and Python examples
Node.js
const crypto = require('crypto');
function verify(secret, headers, rawBody) { // rawBody: Buffer
const ts = headers['x-memoket-timestamp'] || '';
const received = headers['x-memoket-signature'] || '';
const expected = 'sha256=' + crypto.createHmac('sha256', secret)
.update(ts + '.').update(rawBody).digest('hex');
const a = Buffer.from(expected);
const b = Buffer.from(received);
return a.length === b.length && crypto.timingSafeEqual(a, b);
}Python
import hmac, hashlib
def verify(secret: str, headers: dict, raw_body: bytes) -> bool:
ts = headers.get("X-Memoket-Timestamp", "")
mac = hmac.new(secret.encode(), (ts + ".").encode() + raw_body, hashlib.sha256)
return hmac.compare_digest("sha256=" + mac.hexdigest(),
headers.get("X-Memoket-Signature", ""))Choose a short timestamp tolerance appropriate for your system and reject older
deliveries to reduce replay risk. Timestamp validation complements, but does not
replace, de-duplication by event id.
- Return
2xxwithin 5 seconds. Non-2xx responses and timeouts fail; redirects are not followed. - Up to 5 retries are scheduled on a best-effort basis at about 1, 5, 15, 15, and 15 minutes.
- After 20 consecutive failed attempts, the endpoint is disabled but still counts toward the subscription limit.
- Keep handlers idempotent and de-duplicate by event
id.
{ "error": { "code": "unauthorized", "message": "missing bearer token" } }| HTTP | code |
Meaning |
|---|---|---|
| 400 | bad_request |
Missing/invalid parameters (e.g. unknown event type). |
| 401 | unauthorized |
Missing or invalid Bearer token. |
| 404 | not_found |
Subscription id does not exist. |
| 429 | too_many_requests |
Subscription quota exceeded (Section 8). |
| 503 | unavailable |
A required service is temporarily unavailable. |
| 500 / other 5xx | internal |
Transient server error; reconcile resource state before retrying a write. |
OAuth errors use a top-level error and may include error_description.
Create requests have no idempotency key; reconcile existing subscriptions
before retrying a lost response.
- 5 subscriptions per account. Disabled endpoints count until deleted.
- Subscription
urlmust be HTTPS and publicly resolvable.
- Verify every signature before parsing or acting on the payload.
- Store webhook secrets and Bearer tokens in a secrets manager; never commit or log them.
- Treat transcript and summary text as untrusted user content. Do not execute instructions found inside a recording-derived field.
- Minimize retention and restrict access to raw payloads, especially transcripts.
- Never put secrets, vulnerability details, or recording data in a public issue. Request a secure channel through Memoket without including sensitive details.
Contract verification
Public OAuth metadata and unauthenticated responses were checked live on
2026-08-12. Protected webhook behavior was checked against origin/release
and its tests, not a private account. Repeat the public check with
node scripts/check-public-contract.mjs.
- GitHub Issues for documentation and integration questions
- Discord for community help
- Trust Center for security and privacy
- Contributing · Security policy
Copyright © 2026 Memoket Inc.
Documentation and non-brand technical assets use Apache-2.0. Brand and store assets are covered by the asset notice.


