Webhook API
Register, inspect, update, and remove webhook subscriptions for event-driven integrations.
Webhook API
Use webhooks to receive asynchronous notifications for supported platform events.
List Webhooks
webhooks:readPath, query, and header parameters
sizepageResponses
Paginated list of webhook subscriptions.
{
"entities": "Webhook",
"count": 2,
"per_page": 100,
"pages": {
"current": 1,
"max": 1
},
"elements": [
{
"id": "wh_001",
"name": "Billing Events",
"description": "Receive invoice and payment updates",
"url": "https://example.com/webhooks/billing",
"events": [
"Charge.succeeded"
],
"token": "redacted"
}
]
}Request examples
curl --request GET 'https://api.hub.donutwork.com/2026-02-01/webhooks.json' \
--header 'Authorization: Bearer YOUR_API_KEY'const response = await fetch('https://api.hub.donutwork.com/2026-02-01/webhooks.json', {
method: 'GET',
headers: {
Authorization: 'Bearer ' + process.env.DONUTWORK_API_KEY,
},
});
const requestId = response.headers.get('x-request-id');
if (!response.ok) {
const error = await response.text();
throw new Error(`${response.status} ${error} [${requestId}]`);
}
const contentType = response.headers.get('content-type') ?? '';
const data = response.status === 204 ? null
: contentType.includes('application/json') ? await response.json()
: await response.text();$request = curl_init('https://api.hub.donutwork.com/2026-02-01/webhooks.json');
curl_setopt_array($request, [
CURLOPT_CUSTOMREQUEST => 'GET',
CURLOPT_RETURNTRANSFER => true,
CURLOPT_HTTPHEADER => [
'Authorization: Bearer ' . getenv('DONUTWORK_API_KEY'),
],
]);
$response = curl_exec($request);
if ($response === false) {
$error = curl_error($request);
curl_close($request);
throw new RuntimeException($error);
}
$status = curl_getinfo($request, CURLINFO_RESPONSE_CODE);
$contentType = (string) curl_getinfo($request, CURLINFO_CONTENT_TYPE);
curl_close($request);
if ($status >= 400) { throw new RuntimeException($response); }
$data = $status === 204 ? null
: (str_contains($contentType, 'application/json')
? json_decode($response, true, flags: JSON_THROW_ON_ERROR) : $response);Replace uppercase placeholders and sample values with your own data and company resource IDs. Keep the API key on a trusted server. Reuse an idempotency key only for the same logical request and payload.
Create Webhook
webhooks:writePath, query, and header parameters
No path, query, or additional header parameters.
Request Body
{
"webhook": {
"name": "Billing Events",
"description": "Receive invoice and payment updates",
"url": "https://example.com/webhooks/billing",
"events": [
"Charge.succeeded"
]
}
}webhook.nameRequiredwebhook.urlRequiredwebhook.eventsRequiredwebhook.descriptionResponses
Webhook created successfully.
{
"id": "wh_001",
"name": "Billing Events",
"description": "Receive invoice and payment updates",
"url": "https://example.com/webhooks/billing",
"events": [
"Charge.succeeded"
],
"token": "generated_webhook_token"
}Name, url, or events missing; or TLS certificate validation failed.
{
"error": "A valid \"url\" must be specified."
}URL format invalid or unsupported event specified.
{
"error": "Some events are not available"
}Request examples
curl --request POST 'https://api.hub.donutwork.com/2026-02-01/webhooks.json' \
--header 'Authorization: Bearer YOUR_API_KEY' \
--header 'Content-Type: application/json' \
--data '{
"webhook": {
"name": "Billing Events",
"description": "Receive invoice and payment updates",
"url": "https://example.com/webhooks/billing",
"events": [
"Charge.succeeded"
]
}
}'const response = await fetch('https://api.hub.donutwork.com/2026-02-01/webhooks.json', {
method: 'POST',
headers: {
Authorization: 'Bearer ' + process.env.DONUTWORK_API_KEY,
'Content-Type': 'application/json',
},
body: JSON.stringify({
"webhook": {
"name": "Billing Events",
"description": "Receive invoice and payment updates",
"url": "https://example.com/webhooks/billing",
"events": [
"Charge.succeeded"
]
}
}),
});
const requestId = response.headers.get('x-request-id');
if (!response.ok) {
const error = await response.text();
throw new Error(`${response.status} ${error} [${requestId}]`);
}
const contentType = response.headers.get('content-type') ?? '';
const data = response.status === 204 ? null
: contentType.includes('application/json') ? await response.json()
: await response.text();$request = curl_init('https://api.hub.donutwork.com/2026-02-01/webhooks.json');
curl_setopt_array($request, [
CURLOPT_CUSTOMREQUEST => 'POST',
CURLOPT_RETURNTRANSFER => true,
CURLOPT_HTTPHEADER => [
'Authorization: Bearer ' . getenv('DONUTWORK_API_KEY'),
'Content-Type: application/json',
],
CURLOPT_POSTFIELDS => '{
"webhook": {
"name": "Billing Events",
"description": "Receive invoice and payment updates",
"url": "https://example.com/webhooks/billing",
"events": [
"Charge.succeeded"
]
}
}',
]);
$response = curl_exec($request);
if ($response === false) {
$error = curl_error($request);
curl_close($request);
throw new RuntimeException($error);
}
$status = curl_getinfo($request, CURLINFO_RESPONSE_CODE);
$contentType = (string) curl_getinfo($request, CURLINFO_CONTENT_TYPE);
curl_close($request);
if ($status >= 400) { throw new RuntimeException($response); }
$data = $status === 204 ? null
: (str_contains($contentType, 'application/json')
? json_decode($response, true, flags: JSON_THROW_ON_ERROR) : $response);Replace uppercase placeholders and sample values with your own data and company resource IDs. Keep the API key on a trusted server. Reuse an idempotency key only for the same logical request and payload.
Update Webhook
Update an existing subscription with webhooks:write. Provide only the fields
to change; omitted fields and the existing delivery token are preserved. The
request must contain only a webhook object, with at least one supported field,
and the complete JSON body must fit within 64 KiB. company_id, token, IDs
and unknown fields are rejected. The operation is scoped to the API key's company.
An updated URL must use HTTPS without credentials and resolve only to public Internet addresses. The server validates the certificate against the original hostname while connecting to a validated address; no webhook event is sent by the update. A repeated assignment of identical values succeeds.
webhooks:writePath, query, and header parameters
webhookIdRequiredRequest Body
{
"webhook": {
"name": "Billing Notifications",
"events": [
"Charge.succeeded",
"Charge.failed"
]
}
}webhook.namewebhook.descriptionwebhook.urlwebhook.eventsResponses
Updated metadata; the delivery token is preserved and not returned.
{
"id": "0123456789abcdef01234567",
"name": "Billing Notifications",
"description": "",
"url": "https://example.com/webhooks/billing",
"eventsCount": 2,
"events": [
"Charge.succeeded",
"Charge.failed"
]
}No webhook with this ID exists in the authenticated company.
{
"error": "Webhook not found."
}Invalid field, ID, URL or event subscription.
{
"error": "Provide at least one supported webhook field"
}The destination TLS certificate could not be validated.
{
"error": "Webhook TLS certificate could not be validated"
}Request examples
curl --request PUT 'https://api.hub.donutwork.com/2026-02-01/webhooks/WEBHOOKID.json' \
--header 'Authorization: Bearer YOUR_API_KEY' \
--header 'Content-Type: application/json' \
--data '{
"webhook": {
"name": "Billing Notifications",
"events": [
"Charge.succeeded",
"Charge.failed"
]
}
}'const response = await fetch('https://api.hub.donutwork.com/2026-02-01/webhooks/WEBHOOKID.json', {
method: 'PUT',
headers: {
Authorization: 'Bearer ' + process.env.DONUTWORK_API_KEY,
'Content-Type': 'application/json',
},
body: JSON.stringify({
"webhook": {
"name": "Billing Notifications",
"events": [
"Charge.succeeded",
"Charge.failed"
]
}
}),
});
const requestId = response.headers.get('x-request-id');
if (!response.ok) {
const error = await response.text();
throw new Error(`${response.status} ${error} [${requestId}]`);
}
const contentType = response.headers.get('content-type') ?? '';
const data = response.status === 204 ? null
: contentType.includes('application/json') ? await response.json()
: await response.text();$request = curl_init('https://api.hub.donutwork.com/2026-02-01/webhooks/WEBHOOKID.json');
curl_setopt_array($request, [
CURLOPT_CUSTOMREQUEST => 'PUT',
CURLOPT_RETURNTRANSFER => true,
CURLOPT_HTTPHEADER => [
'Authorization: Bearer ' . getenv('DONUTWORK_API_KEY'),
'Content-Type: application/json',
],
CURLOPT_POSTFIELDS => '{
"webhook": {
"name": "Billing Notifications",
"events": [
"Charge.succeeded",
"Charge.failed"
]
}
}',
]);
$response = curl_exec($request);
if ($response === false) {
$error = curl_error($request);
curl_close($request);
throw new RuntimeException($error);
}
$status = curl_getinfo($request, CURLINFO_RESPONSE_CODE);
$contentType = (string) curl_getinfo($request, CURLINFO_CONTENT_TYPE);
curl_close($request);
if ($status >= 400) { throw new RuntimeException($response); }
$data = $status === 204 ? null
: (str_contains($contentType, 'application/json')
? json_decode($response, true, flags: JSON_THROW_ON_ERROR) : $response);Replace uppercase placeholders and sample values with your own data and company resource IDs. Keep the API key on a trusted server. Reuse an idempotency key only for the same logical request and payload.
List Supported Webhook Types
webhooks:readPath, query, and header parameters
No path, query, or additional header parameters.
Responses
List of available webhook event names for the company context.
[
"Charge.succeeded",
"Charge.failed",
"Customer.subscription.past_due",
"Customer.subscription.recovered",
"Customer.subscription.cancelled",
"Customer.subscription.reactivated"
]Request examples
curl --request GET 'https://api.hub.donutwork.com/2026-02-01/webhooks/types.json' \
--header 'Authorization: Bearer YOUR_API_KEY'const response = await fetch('https://api.hub.donutwork.com/2026-02-01/webhooks/types.json', {
method: 'GET',
headers: {
Authorization: 'Bearer ' + process.env.DONUTWORK_API_KEY,
},
});
const requestId = response.headers.get('x-request-id');
if (!response.ok) {
const error = await response.text();
throw new Error(`${response.status} ${error} [${requestId}]`);
}
const contentType = response.headers.get('content-type') ?? '';
const data = response.status === 204 ? null
: contentType.includes('application/json') ? await response.json()
: await response.text();$request = curl_init('https://api.hub.donutwork.com/2026-02-01/webhooks/types.json');
curl_setopt_array($request, [
CURLOPT_CUSTOMREQUEST => 'GET',
CURLOPT_RETURNTRANSFER => true,
CURLOPT_HTTPHEADER => [
'Authorization: Bearer ' . getenv('DONUTWORK_API_KEY'),
],
]);
$response = curl_exec($request);
if ($response === false) {
$error = curl_error($request);
curl_close($request);
throw new RuntimeException($error);
}
$status = curl_getinfo($request, CURLINFO_RESPONSE_CODE);
$contentType = (string) curl_getinfo($request, CURLINFO_CONTENT_TYPE);
curl_close($request);
if ($status >= 400) { throw new RuntimeException($response); }
$data = $status === 204 ? null
: (str_contains($contentType, 'application/json')
? json_decode($response, true, flags: JSON_THROW_ON_ERROR) : $response);Replace uppercase placeholders and sample values with your own data and company resource IDs. Keep the API key on a trusted server. Reuse an idempotency key only for the same logical request and payload.
Get Webhook
webhooks:readPath, query, and header parameters
webhookIdRequiredResponses
Webhook details.
{
"id": "wh_001",
"name": "Billing Events",
"description": "Receive invoice and payment updates",
"url": "https://example.com/webhooks/billing",
"events": [
"Charge.succeeded"
],
"token": "redacted"
}Webhook resource not found.
{
"error": "Webhook not found."
}Request examples
curl --request GET 'https://api.hub.donutwork.com/2026-02-01/webhooks/WEBHOOKID.json' \
--header 'Authorization: Bearer YOUR_API_KEY'const response = await fetch('https://api.hub.donutwork.com/2026-02-01/webhooks/WEBHOOKID.json', {
method: 'GET',
headers: {
Authorization: 'Bearer ' + process.env.DONUTWORK_API_KEY,
},
});
const requestId = response.headers.get('x-request-id');
if (!response.ok) {
const error = await response.text();
throw new Error(`${response.status} ${error} [${requestId}]`);
}
const contentType = response.headers.get('content-type') ?? '';
const data = response.status === 204 ? null
: contentType.includes('application/json') ? await response.json()
: await response.text();$request = curl_init('https://api.hub.donutwork.com/2026-02-01/webhooks/WEBHOOKID.json');
curl_setopt_array($request, [
CURLOPT_CUSTOMREQUEST => 'GET',
CURLOPT_RETURNTRANSFER => true,
CURLOPT_HTTPHEADER => [
'Authorization: Bearer ' . getenv('DONUTWORK_API_KEY'),
],
]);
$response = curl_exec($request);
if ($response === false) {
$error = curl_error($request);
curl_close($request);
throw new RuntimeException($error);
}
$status = curl_getinfo($request, CURLINFO_RESPONSE_CODE);
$contentType = (string) curl_getinfo($request, CURLINFO_CONTENT_TYPE);
curl_close($request);
if ($status >= 400) { throw new RuntimeException($response); }
$data = $status === 204 ? null
: (str_contains($contentType, 'application/json')
? json_decode($response, true, flags: JSON_THROW_ON_ERROR) : $response);Replace uppercase placeholders and sample values with your own data and company resource IDs. Keep the API key on a trusted server. Reuse an idempotency key only for the same logical request and payload.
Delete Webhook
webhooks:writePath, query, and header parameters
webhookIdRequiredResponses
Webhook deletion accepted.
{
"id": "wh_001",
"deleted": true
}Webhook resource not found.
{
"error": "Webhook not found. Unable to delete unknown resource"
}Request examples
curl --request DELETE 'https://api.hub.donutwork.com/2026-02-01/webhooks/WEBHOOKID.json' \
--header 'Authorization: Bearer YOUR_API_KEY'const response = await fetch('https://api.hub.donutwork.com/2026-02-01/webhooks/WEBHOOKID.json', {
method: 'DELETE',
headers: {
Authorization: 'Bearer ' + process.env.DONUTWORK_API_KEY,
},
});
const requestId = response.headers.get('x-request-id');
if (!response.ok) {
const error = await response.text();
throw new Error(`${response.status} ${error} [${requestId}]`);
}
const contentType = response.headers.get('content-type') ?? '';
const data = response.status === 204 ? null
: contentType.includes('application/json') ? await response.json()
: await response.text();$request = curl_init('https://api.hub.donutwork.com/2026-02-01/webhooks/WEBHOOKID.json');
curl_setopt_array($request, [
CURLOPT_CUSTOMREQUEST => 'DELETE',
CURLOPT_RETURNTRANSFER => true,
CURLOPT_HTTPHEADER => [
'Authorization: Bearer ' . getenv('DONUTWORK_API_KEY'),
],
]);
$response = curl_exec($request);
if ($response === false) {
$error = curl_error($request);
curl_close($request);
throw new RuntimeException($error);
}
$status = curl_getinfo($request, CURLINFO_RESPONSE_CODE);
$contentType = (string) curl_getinfo($request, CURLINFO_CONTENT_TYPE);
curl_close($request);
if ($status >= 400) { throw new RuntimeException($response); }
$data = $status === 204 ? null
: (str_contains($contentType, 'application/json')
? json_decode($response, true, flags: JSON_THROW_ON_ERROR) : $response);Replace uppercase placeholders and sample values with your own data and company resource IDs. Keep the API key on a trusted server. Reuse an idempotency key only for the same logical request and payload.