Webhooks
Webhook endpoints, signing secrets, the delivery log, test sends and redelivery.
Set up the SDK client used in the examples
API key or Dashboard session:
import { UpVoiced } from '@upvoiced/sdk';
const apiKey = process.env['UPVOICED_API_KEY']; // uv_live_… (Settings → API keys)
const workspace = process.env['UPVOICED_WORKSPACE_ID']; // sent as X-Workspace
if (!apiKey || !workspace) throw new Error('Set UPVOICED_API_KEY and UPVOICED_WORKSPACE_ID.');
const upvoiced = new UpVoiced({ apiKey, workspace });curl examples read $UPVOICED_API_KEY and $UPVOICED_WORKSPACE_IDfrom your shell. MCP examples are the tools/call request an MCP client sends to the MCP server with the same API key.
GET/v1/webhooks
Webhook endpoints with status and failure counts; never the secret (owner/admin)
| Name | Type | Required |
|---|---|---|
x-workspace | string (uuid) | Yes |
Response 200: 18 fields
| Name | Type | Required |
|---|---|---|
items | array of object | Yes |
items[].id | string (uuid) | Yes |
items[].url | string | Yes |
items[].description | string | Yes |
items[].events | array of string | Yes |
items[].board_ids | array of string (uuid) | Yes |
items[].active | boolean | Yes |
items[].disabled_reason | "consecutive_failures" | null | Yes |
items[].disabled_at | string (date-time) | null | Yes |
items[].consecutive_failures | integervalue ≥ 0 | Yes |
items[].secret_hint | string | Yes |
items[].secret_rotated_at | string (date-time) | null | Yes |
items[].previous_secret_expires_at | string (date-time) | null | Yes |
items[].last_success_at | string (date-time) | null | Yes |
items[].last_failure_at | string (date-time) | null | Yes |
items[].created_at | string (date-time) | Yes |
items[].updated_at | string (date-time) | Yes |
next_cursor | null | Yes |
Errors: 400, 401, 403, 404, 409, 413, 422, 429, 500, 503, all in the standard error shape.
curl 'https://api.upvoiced.com/v1/webhooks' \
-H "Authorization: Bearer $UPVOICED_API_KEY" \
-H "X-Workspace: $UPVOICED_WORKSPACE_ID"const data = await upvoiced.get('/v1/webhooks');No MCP tool for this operation. The MCP server covers the everyday reads and writes.
POST/v1/webhooks
Create an endpoint with an events filter and optional board filter; the signing secret is in this response only (webhooks.max)
| Name | Type | Required |
|---|---|---|
x-workspace | string (uuid) | Yes |
idempotency-key | stringpattern ^[A-Za-z0-9_.:-]{8,128}$ | No |
| Name | Type | Required |
|---|---|---|
url | stringlength ≤ 2048pattern ^https?:\/\/[^/?#@:\s]+(?:[/?][^#\s]*)?$ | Yes |
events | ["*"] | array of "post.created" | "post.updated" | "post.status_changed" | "post.merged" | "post.deleted" | "comment.created" | "vote.created" | "vote.deleted" | "changelog.published" | "end_user.created" | "end_user.deleted" | Yes |
board_ids | array of string (uuid)items ≤ 50default [] | No |
description | stringlength ≤ 200default "" | No |
active | booleandefault true | No |
Response 201: 17 fields
| Name | Type | Required |
|---|---|---|
id | string (uuid) | Yes |
url | string | Yes |
description | string | Yes |
events | array of string | Yes |
board_ids | array of string (uuid) | Yes |
active | boolean | Yes |
disabled_reason | "consecutive_failures" | null | Yes |
disabled_at | string (date-time) | null | Yes |
consecutive_failures | integervalue ≥ 0 | Yes |
secret_hint | string | Yes |
secret_rotated_at | string (date-time) | null | Yes |
previous_secret_expires_at | string (date-time) | null | Yes |
last_success_at | string (date-time) | null | Yes |
last_failure_at | string (date-time) | null | Yes |
created_at | string (date-time) | Yes |
updated_at | string (date-time) | Yes |
secret | string | nullpattern ^whsec_[0-9A-Za-z]{43}$ | Yes |
Errors: 400, 401, 403, 404, 409, 413, 422, 429, 500, 503, all in the standard error shape.
curl -X POST 'https://api.upvoiced.com/v1/webhooks' \
-H "Authorization: Bearer $UPVOICED_API_KEY" \
-H "X-Workspace: $UPVOICED_WORKSPACE_ID" \
-H "Idempotency-Key: $(uuidgen)" \
-H "Content-Type: application/json" \
-d '{
"url": "https://example.com/upvoiced",
"events": [
"*"
]
}'const data = await upvoiced.post('/v1/webhooks', {
body: { url: 'https://example.com/upvoiced', events: ['*'] },
});No MCP tool for this operation. The MCP server covers the everyday reads and writes.
GET/v1/webhooks/{id}
One webhook endpoint (owner/admin)
| Name | Type | Required |
|---|---|---|
id | string (uuid) | Yes |
| Name | Type | Required |
|---|---|---|
x-workspace | string (uuid) | Yes |
Response 200: 16 fields
| Name | Type | Required |
|---|---|---|
id | string (uuid) | Yes |
url | string | Yes |
description | string | Yes |
events | array of string | Yes |
board_ids | array of string (uuid) | Yes |
active | boolean | Yes |
disabled_reason | "consecutive_failures" | null | Yes |
disabled_at | string (date-time) | null | Yes |
consecutive_failures | integervalue ≥ 0 | Yes |
secret_hint | string | Yes |
secret_rotated_at | string (date-time) | null | Yes |
previous_secret_expires_at | string (date-time) | null | Yes |
last_success_at | string (date-time) | null | Yes |
last_failure_at | string (date-time) | null | Yes |
created_at | string (date-time) | Yes |
updated_at | string (date-time) | Yes |
Errors: 400, 401, 403, 404, 409, 413, 422, 429, 500, 503, all in the standard error shape.
curl 'https://api.upvoiced.com/v1/webhooks/0190f5e2-4c1d-7a3b-8c2d-5e6f7a8b9c0d' \
-H "Authorization: Bearer $UPVOICED_API_KEY" \
-H "X-Workspace: $UPVOICED_WORKSPACE_ID"const data = await upvoiced.get('/v1/webhooks/{id}', { path: { id: '0190f5e2-4c1d-7a3b-8c2d-5e6f7a8b9c0d' } });No MCP tool for this operation. The MCP server covers the everyday reads and writes.
PATCH/v1/webhooks/{id}
Change URL, events, boards, description or active; active=true re-enables an auto-disabled endpoint
| Name | Type | Required |
|---|---|---|
id | string (uuid) | Yes |
| Name | Type | Required |
|---|---|---|
x-workspace | string (uuid) | Yes |
| Name | Type | Required |
|---|---|---|
url | stringlength ≤ 2048pattern ^https?:\/\/[^/?#@:\s]+(?:[/?][^#\s]*)?$ | No |
events | ["*"] | array of "post.created" | "post.updated" | "post.status_changed" | "post.merged" | "post.deleted" | "comment.created" | "vote.created" | "vote.deleted" | "changelog.published" | "end_user.created" | "end_user.deleted" | No |
board_ids | array of string (uuid)items ≤ 50 | No |
description | stringlength ≤ 200 | No |
active | boolean | No |
Response 200: 16 fields
| Name | Type | Required |
|---|---|---|
id | string (uuid) | Yes |
url | string | Yes |
description | string | Yes |
events | array of string | Yes |
board_ids | array of string (uuid) | Yes |
active | boolean | Yes |
disabled_reason | "consecutive_failures" | null | Yes |
disabled_at | string (date-time) | null | Yes |
consecutive_failures | integervalue ≥ 0 | Yes |
secret_hint | string | Yes |
secret_rotated_at | string (date-time) | null | Yes |
previous_secret_expires_at | string (date-time) | null | Yes |
last_success_at | string (date-time) | null | Yes |
last_failure_at | string (date-time) | null | Yes |
created_at | string (date-time) | Yes |
updated_at | string (date-time) | Yes |
Errors: 400, 401, 403, 404, 409, 413, 422, 429, 500, 503, all in the standard error shape.
curl -X PATCH 'https://api.upvoiced.com/v1/webhooks/0190f5e2-4c1d-7a3b-8c2d-5e6f7a8b9c0d' \
-H "Authorization: Bearer $UPVOICED_API_KEY" \
-H "X-Workspace: $UPVOICED_WORKSPACE_ID" \
-H "Content-Type: application/json" \
-d '{
"url": "https://example.com/upvoiced"
}'const data = await upvoiced.patch('/v1/webhooks/{id}', {
path: { id: '0190f5e2-4c1d-7a3b-8c2d-5e6f7a8b9c0d' },
body: { url: 'https://example.com/upvoiced' },
});No MCP tool for this operation. The MCP server covers the everyday reads and writes.
DELETE/v1/webhooks/{id}
Delete an endpoint and its delivery log
| Name | Type | Required |
|---|---|---|
id | string (uuid) | Yes |
| Name | Type | Required |
|---|---|---|
x-workspace | string (uuid) | Yes |
Response 200: 1 fields
| Name | Type | Required |
|---|---|---|
ok | true | Yes |
Errors: 400, 401, 403, 404, 409, 413, 422, 429, 500, 503, all in the standard error shape.
curl -X DELETE 'https://api.upvoiced.com/v1/webhooks/0190f5e2-4c1d-7a3b-8c2d-5e6f7a8b9c0d' \
-H "Authorization: Bearer $UPVOICED_API_KEY" \
-H "X-Workspace: $UPVOICED_WORKSPACE_ID"const data = await upvoiced.delete('/v1/webhooks/{id}', { path: { id: '0190f5e2-4c1d-7a3b-8c2d-5e6f7a8b9c0d' } });No MCP tool for this operation. The MCP server covers the everyday reads and writes.
POST/v1/webhooks/{id}/rotate-secret
New signing secret (in this response only); the previous one keeps signing for 24 hours
| Name | Type | Required |
|---|---|---|
id | string (uuid) | Yes |
| Name | Type | Required |
|---|---|---|
x-workspace | string (uuid) | Yes |
idempotency-key | stringpattern ^[A-Za-z0-9_.:-]{8,128}$ | No |
Response 200: 17 fields
| Name | Type | Required |
|---|---|---|
id | string (uuid) | Yes |
url | string | Yes |
description | string | Yes |
events | array of string | Yes |
board_ids | array of string (uuid) | Yes |
active | boolean | Yes |
disabled_reason | "consecutive_failures" | null | Yes |
disabled_at | string (date-time) | null | Yes |
consecutive_failures | integervalue ≥ 0 | Yes |
secret_hint | string | Yes |
secret_rotated_at | string (date-time) | null | Yes |
previous_secret_expires_at | string (date-time) | null | Yes |
last_success_at | string (date-time) | null | Yes |
last_failure_at | string (date-time) | null | Yes |
created_at | string (date-time) | Yes |
updated_at | string (date-time) | Yes |
secret | string | nullpattern ^whsec_[0-9A-Za-z]{43}$ | Yes |
Errors: 400, 401, 403, 404, 409, 413, 422, 429, 500, 503, all in the standard error shape.
curl -X POST 'https://api.upvoiced.com/v1/webhooks/0190f5e2-4c1d-7a3b-8c2d-5e6f7a8b9c0d/rotate-secret' \
-H "Authorization: Bearer $UPVOICED_API_KEY" \
-H "X-Workspace: $UPVOICED_WORKSPACE_ID" \
-H "Idempotency-Key: $(uuidgen)"const data = await upvoiced.post('/v1/webhooks/{id}/rotate-secret', { path: { id: '0190f5e2-4c1d-7a3b-8c2d-5e6f7a8b9c0d' } });No MCP tool for this operation. The MCP server covers the everyday reads and writes.
GET/v1/webhooks/{id}/deliveries
Delivery log, newest first (14-day retention): attempts, status code, duration, error, response snippet
| Name | Type | Required |
|---|---|---|
id | string (uuid) | Yes |
| Name | Type | Required |
|---|---|---|
status | "pending" | "sending" | "succeeded" | "failed" | "canceled" | No |
cursor | stringlength 1–512 | No |
limit | integervalue 1–100default 25 | No |
| Name | Type | Required |
|---|---|---|
x-workspace | string (uuid) | Yes |
Response 200: 17 fields
| Name | Type | Required |
|---|---|---|
items | array of objectitems ≤ 100 | Yes |
items[].id | string (uuid) | Yes |
items[].webhook_id | string (uuid) | Yes |
items[].event_id | string | null | Yes |
items[].event_type | string | Yes |
items[].kind | "event" | "test" | "redelivery" | Yes |
items[].status | "pending" | "sending" | "succeeded" | "failed" | "canceled" | Yes |
items[].attempt | integervalue ≥ 0 | Yes |
items[].next_attempt_at | string (date-time) | null | Yes |
items[].status_code | integer | nullvalue 100–599 | Yes |
items[].response_ms | integer | nullvalue ≥ 0 | Yes |
items[].error | string | null | Yes |
items[].response_snippet | string | null | Yes |
items[].payload | object | Yes |
items[].created_at | string (date-time) | Yes |
items[].completed_at | string (date-time) | null | Yes |
next_cursor | string | null | Yes |
Errors: 400, 401, 403, 404, 409, 413, 422, 429, 500, 503, all in the standard error shape.
curl 'https://api.upvoiced.com/v1/webhooks/0190f5e2-4c1d-7a3b-8c2d-5e6f7a8b9c0d/deliveries' \
-H "Authorization: Bearer $UPVOICED_API_KEY" \
-H "X-Workspace: $UPVOICED_WORKSPACE_ID"const data = await upvoiced.get('/v1/webhooks/{id}/deliveries', { path: { id: '0190f5e2-4c1d-7a3b-8c2d-5e6f7a8b9c0d' } });No MCP tool for this operation. The MCP server covers the everyday reads and writes.
POST/v1/webhooks/{id}/test
Queue a signed webhook.test delivery to the endpoint (attempted once; 30/hour)
| Name | Type | Required |
|---|---|---|
id | string (uuid) | Yes |
| Name | Type | Required |
|---|---|---|
x-workspace | string (uuid) | Yes |
idempotency-key | stringpattern ^[A-Za-z0-9_.:-]{8,128}$ | No |
Response 202: 15 fields
| Name | Type | Required |
|---|---|---|
id | string (uuid) | Yes |
webhook_id | string (uuid) | Yes |
event_id | string | null | Yes |
event_type | string | Yes |
kind | "event" | "test" | "redelivery" | Yes |
status | "pending" | "sending" | "succeeded" | "failed" | "canceled" | Yes |
attempt | integervalue ≥ 0 | Yes |
next_attempt_at | string (date-time) | null | Yes |
status_code | integer | nullvalue 100–599 | Yes |
response_ms | integer | nullvalue ≥ 0 | Yes |
error | string | null | Yes |
response_snippet | string | null | Yes |
payload | object | Yes |
created_at | string (date-time) | Yes |
completed_at | string (date-time) | null | Yes |
Errors: 400, 401, 403, 404, 409, 413, 422, 429, 500, 503, all in the standard error shape.
curl -X POST 'https://api.upvoiced.com/v1/webhooks/0190f5e2-4c1d-7a3b-8c2d-5e6f7a8b9c0d/test' \
-H "Authorization: Bearer $UPVOICED_API_KEY" \
-H "X-Workspace: $UPVOICED_WORKSPACE_ID" \
-H "Idempotency-Key: $(uuidgen)"const data = await upvoiced.post('/v1/webhooks/{id}/test', { path: { id: '0190f5e2-4c1d-7a3b-8c2d-5e6f7a8b9c0d' } });No MCP tool for this operation. The MCP server covers the everyday reads and writes.
POST/v1/webhook-deliveries/{id}/redeliver
Replay a logged delivery with the same body and a fresh signature (attempted once; 30/hour)
| Name | Type | Required |
|---|---|---|
id | string (uuid) | Yes |
| Name | Type | Required |
|---|---|---|
x-workspace | string (uuid) | Yes |
idempotency-key | stringpattern ^[A-Za-z0-9_.:-]{8,128}$ | No |
Response 202: 15 fields
| Name | Type | Required |
|---|---|---|
id | string (uuid) | Yes |
webhook_id | string (uuid) | Yes |
event_id | string | null | Yes |
event_type | string | Yes |
kind | "event" | "test" | "redelivery" | Yes |
status | "pending" | "sending" | "succeeded" | "failed" | "canceled" | Yes |
attempt | integervalue ≥ 0 | Yes |
next_attempt_at | string (date-time) | null | Yes |
status_code | integer | nullvalue 100–599 | Yes |
response_ms | integer | nullvalue ≥ 0 | Yes |
error | string | null | Yes |
response_snippet | string | null | Yes |
payload | object | Yes |
created_at | string (date-time) | Yes |
completed_at | string (date-time) | null | Yes |
Errors: 400, 401, 403, 404, 409, 413, 422, 429, 500, 503, all in the standard error shape.
curl -X POST 'https://api.upvoiced.com/v1/webhook-deliveries/0190f5e2-4c1d-7a3b-8c2d-5e6f7a8b9c0d/redeliver' \
-H "Authorization: Bearer $UPVOICED_API_KEY" \
-H "X-Workspace: $UPVOICED_WORKSPACE_ID" \
-H "Idempotency-Key: $(uuidgen)"const data = await upvoiced.post('/v1/webhook-deliveries/{id}/redeliver', { path: { id: '0190f5e2-4c1d-7a3b-8c2d-5e6f7a8b9c0d' } });No MCP tool for this operation. The MCP server covers the everyday reads and writes.