A valid request URL is required to generate request examples{
"webhook": {
"webhookId": "<string>",
"url": "<string>",
"events": [
"run.ended"
],
"enabled": true,
"secretHint": "<string>",
"createdAt": "2023-11-07T05:31:56Z",
"secretRotatedAt": "2023-11-07T05:31:56Z"
},
"secret": "<string>"
}{
"error": {
"code": "backend_resolution_failed",
"message": "<string>",
"retryable": true
}
}{
"error": {
"code": "unauthorized",
"reason": "missing_credentials",
"hint": "<string>"
}
}{
"error": {
"code": "backend_resolution_failed",
"message": "<string>",
"retryable": true
}
}{
"error": {
"code": "payload_too_large",
"message": "<string>"
}
}{
"error": {
"code": "rate_limited",
"message": "<string>"
}
}{
"error": {
"code": "backend_resolution_failed",
"message": "<string>",
"retryable": true
}
}Subscribe your system to what happens here
Creates a subscription and returns its signing secret ONCE. Store the secret where your receiver can read it: to get another you have to rotate, which stops the old one working.
HOW TO VERIFY A DELIVERY. Every request carries Phonebase-Signature: t=<unix seconds>,v1=<hex>, where the hex is HMAC-SHA256 of <t>.<raw body> under your secret. Compute it over the RAW bytes you received, before any JSON parsing, compare in constant time, and reject anything whose t is further from your own clock than you are willing to accept. The timestamp is inside the signed material precisely so that a captured delivery cannot be replayed at you a year later.
DELIVERY IS AT LEAST ONCE. Phonebase-Delivery is the same on every retry of one delivery, so deduplicate on it. A non-2xx answer is retried 5 times in all, over about twenty five minutes, and then given up on. Redirects are NOT followed: a 30x is recorded as the answer it is, because following one would send your payload to a url nobody checked.
The destination must be https and must be reachable on the public internet. An address inside a private network is refused because this service would be the one making the request.
Hosted deployments only. A local checkout does not mount this route, so calling it there is a 404.
A valid request URL is required to generate request examples{
"webhook": {
"webhookId": "<string>",
"url": "<string>",
"events": [
"run.ended"
],
"enabled": true,
"secretHint": "<string>",
"createdAt": "2023-11-07T05:31:56Z",
"secretRotatedAt": "2023-11-07T05:31:56Z"
},
"secret": "<string>"
}{
"error": {
"code": "backend_resolution_failed",
"message": "<string>",
"retryable": true
}
}{
"error": {
"code": "unauthorized",
"reason": "missing_credentials",
"hint": "<string>"
}
}{
"error": {
"code": "backend_resolution_failed",
"message": "<string>",
"retryable": true
}
}{
"error": {
"code": "payload_too_large",
"message": "<string>"
}
}{
"error": {
"code": "rate_limited",
"message": "<string>"
}
}{
"error": {
"code": "backend_resolution_failed",
"message": "<string>",
"retryable": true
}
}Authorizations
The control surface credential. Send Authorization: Bearer <token>.
Two kinds of token are accepted and they are told apart by shape, not by a separate header. A token beginning pbk_ is an org scoped API key, whose public half and secret half are generated together and of which only a hash of the secret is ever stored; anything else is treated as an OAuth 2.1 access token and verified against the authorization server's keys.
Both resolve to the same context: an org, a principal and a set of scopes. Nothing downstream branches on which channel you used, with one deliberate exception, key management, which requires a signed-in person so that a key can never mint another key.
Scopes are enforced when MCP tools are REGISTERED rather than when they are called, so a tool your credential cannot use is absent from tools/list rather than refused mid gesture.
Body
Where to post, and what to post about.
An https url, at most 2000 characters, with no credentials in it.
2000At least one event. An unknown name is refused rather than ignored.
1An event to subscribe to.
run.ended, run.needs_user_control, schedule.triggered, fulfilment.changed