Create or replace a webhook subscription
Subscribe an endpoint to call events.
Subscribe an endpoint to call events.
The storage key is (organization, agent_id, callback_url), so POSTing the
same agent and the same URL twice UPDATES the existing subscription rather
than creating a twin that would deliver every event twice. That is what makes
this endpoint safe to call from an idempotent provisioning script.
Scope. With agent_id set, it must be an agent in your organisation —
any other value answers 404 — and the subscription applies to that agent
only. Without it (or with an empty string) it becomes the organisation-wide
default. Resolution is an OVERRIDE, not a union: if an agent has any enabled
subscription of its own, only those are consulted for that agent's calls —
even for an event none of them asked for. An agent whose only subscription
is paused falls back to the organisation-wide default.
Events. Valid event types: all_processing_completed,
call_completed, call_corrected, call_started, client_analysis_completed,
platform_analysis_completed, recording_completed. Omitting event_types,
or sending an empty list, resolves to all_processing_completed — there is
no value that means "every event". An unknown event type is
rejected here rather than silently dropped.
Secrets and headers are write-only. The signing secret and every header
value are stored encrypted and are never returned by any endpoint — reads
give you has_secret, a "***" preview, and the header NAMES. Keep your
own copy of the secret.
The callback URL is checked twice. It is refused here if it points at a
private, loopback, link-local, metadata or reserved address. A hostname is
accepted without a DNS lookup, and the delivery engine re-checks the
RESOLVED addresses before every send — so a URL accepted here can still be
refused later, which shows up in the ledger as
suppressed_reason: "url_rejected".
The API runs several processes, each with its own 60-second subscription cache. A change is live immediately for the process that served this request and within 60 seconds everywhere else.
Errors: 400 (bad event type, method, header or URL), 401, 403,
404 (agent_id is not an agent in your organisation), 422, 429, 503.
Rate limit: 1000 requests per minute per organization (bucket default). Exceeding it returns 429 with Retry-After; the X-RateLimit-* response headers report your remaining allowance on every call.
v2/webhooksAuthorization
AuthorizationRequiredBearer <token>
Your Graine API key. Create one in the dashboard under Developers, or via POST /v2/api-keys. Send it as Authorization: Bearer <key>.
In: header
Request Body
application/jsonRequiredcallback_urlRequiredCallback Url
Public https endpoint we POST the event to.
1Maximum length: 2048agent_idAgent Id | null
Scope this subscription to one agent. Omit it (or send an empty string) for the organisation-wide default.
event_typesEvent Types | null
Valid event types: all_processing_completed, call_completed, call_corrected, call_ringing, call_started, client_analysis_completed, platform_analysis_completed, recording_completed. Omitting event_types, or sending an empty list, resolves to all_processing_completed — there is no value that means "every event".
method_typeMethod Type
POST or PUT.
"POST"headersHeaders | null
Extra request headers, at most 20. Values are stored encrypted and are NEVER returned. Content-Type, Content-Length, Host and any X-Graine-* name are refused.
secretSecret | null
HMAC-SHA256 signing secret. Sent back only as '***'; keep your own copy. Omit it to fall back to the organisation signing secret.
enabledEnabled
Set false to create it paused.
trueQuery Parameters
organization_idOrganization Id
Must match the organisation the API key belongs to.
Response Body
201
The stored subscription. Header values and the secret are never returned.
subscription_idRequiredSubscription Id
Stable id for this subscription.
organization_idRequiredOrganization Id
Always the organisation the API key belongs to.
agent_idAgent Id | null
The agent this subscription is scoped to, or null for the organisation-wide default.
scopeRequiredScope
'agent' when agent_id is set, 'organization' for the org-wide default. An agent that has ANY enabled subscription of its own uses only those; the org-wide default is a fallback, not a union.
event_typesRequiredEvent Types
Resolved event list — never null, never empty.
@minItems 0
@minItems 0
@minItems 0
callback_urlRequiredCallback Url
The endpoint we POST to.
method_typeRequiredMethod Type
POST or PUT.
headersRequiredHeaders
Configured header names, values masked as '***'. Values are never returned.
header_namesRequiredHeader Names
The same header names as a sorted list, for clients that want it flat.
@minItems 0
@minItems 0
@minItems 0
enabledRequiredEnabled
A disabled subscription receives nothing.
has_secretRequiredHas Secret
True when a signing secret is configured.
secretSecret | null
Masked preview ('***') when a secret is set, otherwise null.
secret_unavailableRequiredSecret Unavailable
True when the stored secret can no longer be decrypted. Deliveries go out UNSIGNED while this is true and your signature check will never pass — PATCH a fresh secret to fix it.
created_atCreated At | null
ISO-8601 UTC.
updated_atUpdated At | null
ISO-8601 UTC.
created_byCreated By | null
Who created it.
400
1000 — malformed input the schema could not reject: an unknown event type, a method other than POST/PUT, a reserved header name, or a callback_url pointing at a private, loopback or metadata address.
errorRequiredError
Stable integer code from the /v2 error table. Branch on this.
messageRequiredMessage
One human-readable sentence. Wording may change; the code will not.
401
1100 — missing, unknown or inactive API key. 1101 — a browser session token was presented instead of a gat_ key.
errorRequiredError
Stable integer code from the /v2 error table. Branch on this.
messageRequiredMessage
One human-readable sentence. Wording may change; the code will not.
403
1102 — the key is valid but lacks the scope this endpoint requires, or names another organisation. GET /v2/scopes reports which scopes the key holds.
errorRequiredError
Stable integer code from the /v2 error table. Branch on this.
messageRequiredMessage
One human-readable sentence. Wording may change; the code will not.
404
1200 — no such subscription or delivery in this organisation. Cross-tenant reads answer 404, never 403.
errorRequiredError
Stable integer code from the /v2 error table. Branch on this.
messageRequiredMessage
One human-readable sentence. Wording may change; the code will not.
422
1001 — the body or query failed validation; the message names the first offending field.
errorRequiredinteger
Stable integer code from the error table. Branch on this, not on the message.
messageRequiredstring
One human-readable sentence. Wording may change; the code will not.
429
1300 — per-organisation rate limit exceeded. Carries Retry-After.
errorRequiredError
Stable integer code from the /v2 error table. Branch on this.
messageRequiredMessage
One human-readable sentence. Wording may change; the code will not.
503
1501 — the webhook subscription store is unreachable.
errorRequiredError
Stable integer code from the /v2 error table. Branch on this.
messageRequiredMessage
One human-readable sentence. Wording may change; the code will not.
504
1502 — an upstream did not respond in time.
errorRequiredError
Stable integer code from the /v2 error table. Branch on this.
messageRequiredMessage
One human-readable sentence. Wording may change; the code will not.
The stored subscription. Header values and the secret are never returned.

