Register a webhook endpoint
using System.Net.Http.Headers;var client = new HttpClient();var request = new HttpRequestMessage{ Method = HttpMethod.Post, RequestUri = new Uri("https://api.roundrobinbot.eu/v1/webhooks"), Headers = { { "Authorization", "Bearer <token>" }, }, Content = new StringContent("{ \"name\": \"example\", \"url\": \"example\", \"eventTypes\": [ \"example\" ] }") { Headers = { ContentType = new MediaTypeHeaderValue("application/json-patch+json") } }};using (var response = await client.SendAsync(request)){ response.EnsureSuccessStatusCode(); var body = await response.Content.ReadAsStringAsync(); Console.WriteLine(body);}package main
import ( "fmt" "strings" "net/http" "io")
func main() {
url := "https://api.roundrobinbot.eu/v1/webhooks"
payload := strings.NewReader("{ \"name\": \"example\", \"url\": \"example\", \"eventTypes\": [ \"example\" ] }")
req, _ := http.NewRequest("POST", url, payload)
req.Header.Add("Authorization", "Bearer <token>") req.Header.Add("Content-Type", "application/json-patch+json")
res, _ := http.DefaultClient.Do(req)
defer res.Body.Close() body, _ := io.ReadAll(res.Body)
fmt.Println(res) fmt.Println(string(body))
}OkHttpClient client = new OkHttpClient();
MediaType mediaType = MediaType.parse("application/json-patch+json");RequestBody body = RequestBody.create(mediaType, "{ \"name\": \"example\", \"url\": \"example\", \"eventTypes\": [ \"example\" ] }");Request request = new Request.Builder() .url("https://api.roundrobinbot.eu/v1/webhooks") .post(body) .addHeader("Authorization", "Bearer <token>") .addHeader("Content-Type", "application/json-patch+json") .build();
Response response = client.newCall(request).execute();import axios from 'axios';
const options = { method: 'POST', url: 'https://api.roundrobinbot.eu/v1/webhooks', headers: {Authorization: 'Bearer <token>', 'Content-Type': 'application/json-patch+json'}, data: '{ "name": "example", "url": "example", "eventTypes": [ "example" ] }'};
try { const { data } = await axios.request(options); console.log(data);} catch (error) { console.error(error);}const url = 'https://api.roundrobinbot.eu/v1/webhooks';const options = { method: 'POST', headers: {Authorization: 'Bearer <token>', 'Content-Type': 'application/json-patch+json'}, body: '{ "name": "example", "url": "example", "eventTypes": [ "example" ] }'};
try { const response = await fetch(url, options); const data = await response.json(); console.log(data);} catch (error) { console.error(error);}curl --request POST \ --url https://api.roundrobinbot.eu/v1/webhooks \ --header 'Authorization: Bearer <token>' \ --header 'Content-Type: application/json-patch+json' \ --data '{ "name": "example", "url": "example", "eventTypes": [ "example" ] }'Registers a destination for every rotation in the workspace and answers with the signing secret, once: nothing returns it again, and a caller who loses it rotates rather than recovering it. The URL is judged before anything is stored: not https, or resolving into a private, loopback, link-local or cloud-metadata range, is a 400 with a code saying which. Credentials embedded in it are removed, and the endpoint in the response carries the URL we will actually dial. No precondition header. There is no prior version to match, and unlike a rotation a duplicate registration is visible in a list of at most ten rows and deleted in one call, so an Idempotency-Key would buy a retry nothing it cannot already see.
Authorizations
Section titled “Authorizations”Request Body
Section titled “Request Body”Registers a destination for the whole workspace.
object
What the receiving system is called, in words. Required.
Where Round Robin posts. Required, must be https, and must not resolve into a private, loopback, link-local or cloud-metadata range.
Which events this endpoint wants: duty.changed, nobody.on_call, rotation.created, rotation.updated, rotation.deleted. Required.
Examplegenerated
{ "name": "example", "url": "example", "eventTypes": [ "example" ]}Registers a destination for the whole workspace.
object
What the receiving system is called, in words. Required.
Where Round Robin posts. Required, must be https, and must not resolve into a private, loopback, link-local or cloud-metadata range.
Which events this endpoint wants: duty.changed, nobody.on_call, rotation.created, rotation.updated, rotation.deleted. Required.
Examplegenerated
{ "name": "example", "url": "example", "eventTypes": [ "example" ]}Registers a destination for the whole workspace.
object
What the receiving system is called, in words. Required.
Where Round Robin posts. Required, must be https, and must not resolve into a private, loopback, link-local or cloud-metadata range.
Which events this endpoint wants: duty.changed, nobody.on_call, rotation.created, rotation.updated, rotation.deleted. Required.
Examplegenerated
{ "name": "example", "url": "example", "eventTypes": [ "example" ]}Registers a destination for the whole workspace.
object
What the receiving system is called, in words. Required.
Where Round Robin posts. Required, must be https, and must not resolve into a private, loopback, link-local or cloud-metadata range.
Which events this endpoint wants: duty.changed, nobody.on_call, rotation.created, rotation.updated, rotation.deleted. Required.
Examplegenerated
{ "name": "example", "url": "example", "eventTypes": [ "example" ]}Responses
Section titled “Responses”Created
A newly registered endpoint, and one of the two moments its signing secret exists outside your hands.
object
A registered destination: where Round Robin posts this workspace’s events, what it is subscribed to, and whether we are still calling it. Never the signing secret.
object
What the receiving system is called. Shown in the dashboard, never used for lookup.
Where we post. Always https.
What this endpoint receives: duty.changed, nobody.on_call, rotation.created, rotation.updated, rotation.deleted.
enabled or disabled. There is no revoked state: delete the endpoint instead.
Why we stopped calling: manual, delivery_failures or plan_downgrade. Absent while the endpoint is enabled.
Which secret is signing now, so your logs and ours name the same one.
The outgoing secret during a rotation’s grace window, absent at every other time.
When the outgoing secret stops signing. Until then both signatures travel in one header, so a receiver can cut over without an outage.
When the endpoint was registered.
Sign-verification secret, returned exactly once.
Examplegenerated
{ "endpoint": { "id": "example", "name": "example", "url": "example", "eventTypes": [ "example" ], "state": "example", "disabledReason": "example", "currentSecretId": "example", "currentSecretCreatedAt": "2026-04-15T12:00:00Z", "previousSecretId": "example", "previousSecretExpiresAt": "2026-04-15T12:00:00Z", "createdAt": "2026-04-15T12:00:00Z", "updatedAt": "2026-04-15T12:00:00Z" }, "signingSecret": "example"}Bad Request
object
Examplegenerated
{ "type": "example", "title": "example", "status": 1, "detail": "example", "instance": "example"}Unauthorized
object
Examplegenerated
{ "type": "example", "title": "example", "status": 1, "detail": "example", "instance": "example"}Payment Required
object
Examplegenerated
{ "type": "example", "title": "example", "status": 1, "detail": "example", "instance": "example"}Forbidden
object
Examplegenerated
{ "type": "example", "title": "example", "status": 1, "detail": "example", "instance": "example"}Not Found
object
Examplegenerated
{ "type": "example", "title": "example", "status": 1, "detail": "example", "instance": "example"}Conflict
object
Examplegenerated
{ "type": "example", "title": "example", "status": 1, "detail": "example", "instance": "example"}