Skip to content

Webhooks

Receive real-time notifications when events happen in your SkipUp workspace.

Webhooks let you subscribe to events in your SkipUp workspace. When an event occurs, SkipUp sends an HTTP POST request to your configured URL with a JSON payload describing what happened.

FieldTypeDescription
idstringUnique identifier for the webhook endpoint.
urlstringThe URL that receives webhook deliveries.
descriptionstring | nullA human-readable description of the endpoint.
enabledbooleanWhether the endpoint is currently active.
eventsstring[]List of event types this endpoint subscribes to.
disabled_atstring | nullISO 8601 timestamp of when the endpoint was disabled.
disabled_reasonstring | nullReason the endpoint was disabled (e.g., too_many_failures).
created_atstringISO 8601 timestamp of when the endpoint was created.
updated_atstringISO 8601 timestamp of the last update.
FieldTypeDescription
idstringUnique identifier for the delivery attempt.
event_typestringThe event type that triggered this delivery.
statusstringDelivery status: pending, success, or failed.
payloadobjectThe JSON payload that was sent.
response_codeinteger | nullHTTP response code from your server.
attemptsintegerNumber of delivery attempts made.
delivered_atstring | nullISO 8601 timestamp of successful delivery.
last_attempted_atstring | nullISO 8601 timestamp of the most recent attempt.
created_atstringISO 8601 timestamp of when the delivery was created.
GET /api/v1/webhook_endpoints

Returns a paginated list of webhook endpoints in your workspace, sorted by creation date (newest first).

Scope required: webhooks.read

ParameterTypeDescription
limitintegerNumber of results per page. Default 25, max 100.
cursorstringCursor for fetching the next page.
Terminal window
curl https://api.skipup.ai/api/v1/webhook_endpoints \
-H "Authorization: Bearer $SKIPUP_API_KEY"
{
"data": [
{
"id": "we_01HQ...",
"url": "https://example.com/webhooks/skipup",
"description": "Production webhook handler",
"enabled": true,
"events": ["meeting_request.booked", "meeting_request.cancelled"],
"disabled_at": null,
"disabled_reason": null,
"created_at": "2025-01-10T12:00:00Z",
"updated_at": "2025-01-10T12:00:00Z"
}
],
"meta": {
"limit": 25,
"has_more": false
}
}
GET /api/v1/webhook_endpoints/:id

Retrieves a single webhook endpoint by ID.

Scope required: webhooks.read

Terminal window
curl https://api.skipup.ai/api/v1/webhook_endpoints/we_01HQ... \
-H "Authorization: Bearer $SKIPUP_API_KEY"
{
"data": {
"id": "we_01HQ...",
"url": "https://example.com/webhooks/skipup",
"description": "Production webhook handler",
"enabled": true,
"events": ["meeting_request.booked", "meeting_request.cancelled"],
"disabled_at": null,
"disabled_reason": null,
"created_at": "2025-01-10T12:00:00Z",
"updated_at": "2025-01-10T12:00:00Z"
}
}
POST /api/v1/webhook_endpoints

Creates a new webhook endpoint. The endpoint starts receiving deliveries immediately.

Scope required: webhooks.write

ParameterTypeRequiredDescription
urlstringYesThe HTTPS URL to receive webhook deliveries.
eventsstring[]YesEvent types to subscribe to. See Event types.
descriptionstringNoA human-readable description.
headersobject[]NoCustom HTTP headers to include with each delivery. Each object has key and value fields.
Terminal window
curl -X POST https://api.skipup.ai/api/v1/webhook_endpoints \
-H "Authorization: Bearer $SKIPUP_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"url": "https://example.com/webhooks/skipup",
"events": ["meeting_request.booked", "meeting_request.cancelled"],
"description": "Production webhook handler",
"headers": [
{"key": "X-Custom-Auth", "value": "my-secret-token"}
]
}'
{
"data": {
"id": "we_01HQ...",
"url": "https://example.com/webhooks/skipup",
"description": "Production webhook handler",
"enabled": true,
"events": ["meeting_request.booked", "meeting_request.cancelled"],
"disabled_at": null,
"disabled_reason": null,
"created_at": "2025-01-10T12:00:00Z",
"updated_at": "2025-01-10T12:00:00Z"
}
}
PATCH /api/v1/webhook_endpoints/:id

Updates an existing webhook endpoint. You can modify any combination of fields.

Scope required: webhooks.write

ParameterTypeRequiredDescription
urlstringNoUpdated delivery URL.
eventsstring[]NoUpdated list of event types to subscribe to.
descriptionstringNoUpdated description.
enabledbooleanNoSet to false to pause deliveries, true to resume.
Terminal window
curl -X PATCH https://api.skipup.ai/api/v1/webhook_endpoints/we_01HQ... \
-H "Authorization: Bearer $SKIPUP_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"events": ["meeting_request.booked", "meeting_request.cancelled", "meeting_request.created"],
"enabled": true
}'
{
"data": {
"id": "we_01HQ...",
"url": "https://example.com/webhooks/skipup",
"description": "Production webhook handler",
"enabled": true,
"events": ["meeting_request.booked", "meeting_request.cancelled", "meeting_request.created"],
"disabled_at": null,
"disabled_reason": null,
"created_at": "2025-01-10T12:00:00Z",
"updated_at": "2025-01-20T09:00:00Z"
}
}
DELETE /api/v1/webhook_endpoints/:id

Permanently deletes a webhook endpoint. Pending deliveries for this endpoint will not be sent.

Scope required: webhooks.write

Terminal window
curl -X DELETE https://api.skipup.ai/api/v1/webhook_endpoints/we_01HQ... \
-H "Authorization: Bearer $SKIPUP_API_KEY"

No response body.

POST /api/v1/webhook_endpoints/:id/test

Sends a test delivery to the endpoint. Use this to verify your webhook handler is working correctly.

Scope required: webhooks.write

Terminal window
curl -X POST https://api.skipup.ai/api/v1/webhook_endpoints/we_01HQ.../test \
-H "Authorization: Bearer $SKIPUP_API_KEY"
{
"data": {
"delivery_id": "wd_01HQ...",
"status": "queued"
}
}

The test delivery is queued and sent asynchronously. The payload contains:

{
"event": "test",
"timestamp": "2025-01-20T14:30:00Z",
"message": "Test webhook delivery"
}
GET /api/v1/webhook_endpoints/:webhook_endpoint_id/deliveries

Returns a paginated list of webhook deliveries for a specific endpoint, sorted by creation date (newest first).

Scope required: webhooks.read

ParameterTypeDescription
statusstringFilter by delivery status: pending, success, or failed.
event_typestringFilter by event type (e.g., meeting_request.booked).
limitintegerNumber of results per page. Default 25, max 100.
cursorstringCursor for fetching the next page.
Terminal window
curl "https://api.skipup.ai/api/v1/webhook_endpoints/we_01HQ.../deliveries?status=failed" \
-H "Authorization: Bearer $SKIPUP_API_KEY"
{
"data": [
{
"id": "wd_01HQ...",
"event_type": "meeting_request.booked",
"status": "failed",
"payload": {
"event": "meeting_request.booked",
"timestamp": "2025-01-20T14:30:00Z",
"id": "mr_01HQ...",
"organizer_email": "[email protected]",
"status": "booked",
"booked_at": "2025-01-20T14:30:00Z"
},
"response_code": 500,
"attempts": 8,
"delivered_at": null,
"last_attempted_at": "2025-01-20T16:00:00Z",
"created_at": "2025-01-20T14:30:00Z"
}
],
"meta": {
"limit": 25,
"has_more": false
}
}

Subscribe to these events when creating or updating a webhook endpoint.

Fired when a new meeting request is created.

{
"event": "meeting_request.created",
"timestamp": "2025-01-20T14:30:00Z",
"id": "mr_01HQ...",
"organizer_email": "[email protected]",
"participant_emails": ["[email protected]", "[email protected]"],
"status": "active",
"created_at": "2025-01-20T14:30:00Z"
}

Fired when a meeting request has been successfully scheduled and calendar invites sent.

{
"event": "meeting_request.booked",
"timestamp": "2025-01-20T16:00:00Z",
"id": "mr_01HQ...",
"organizer_email": "[email protected]",
"calendar_event_id": "evt_abc123",
"status": "booked",
"booked_at": "2025-01-20T16:00:00Z"
}

Fired when a meeting request is cancelled.

{
"event": "meeting_request.cancelled",
"timestamp": "2025-01-21T10:00:00Z",
"id": "mr_01HQ...",
"organizer_email": "[email protected]",
"status": "cancelled",
"cancelled_at": "2025-01-21T10:00:00Z",
"was_booked": false
}

The was_booked field indicates whether the meeting had been previously booked before it was cancelled.

Fired when a meeting request is paused.

{
"event": "meeting_request.paused",
"timestamp": "2025-01-22T09:00:00Z",
"id": "mr_01HQ...",
"organizer_email": "[email protected]",
"status": "paused",
"paused_at": "2025-01-22T09:00:00Z"
}

Fired when a paused meeting request is resumed back to active scheduling.

{
"event": "meeting_request.resumed",
"timestamp": "2025-01-23T11:00:00Z",
"id": "mr_01HQ...",
"organizer_email": "[email protected]",
"status": "active",
"resumed_at": "2025-01-23T11:00:00Z"
}

Fired when the organizer of a meeting request is changed.

{
"event": "meeting_request.organizer_changed",
"timestamp": "2025-01-21T11:00:00Z",
"id": "mr_01HQ...",
"old_organizer_email": "[email protected]",
"new_organizer_email": "[email protected]"
}

Fired when SkipUp sends a follow-up email to participants who haven’t responded.

Fired when a workspace member is deactivated.

Sent when you use the test endpoint. Useful for verifying your webhook handler is reachable and working.

{
"event": "test",
"timestamp": "2025-01-20T14:30:00Z",
"message": "Test webhook delivery"
}

Every webhook delivery includes a signature in the X-Webhook-Signature header so you can verify that the request came from SkipUp and hasn’t been tampered with.

HeaderDescription
X-Webhook-SignatureHMAC-SHA256 signature of the payload.
X-Webhook-TimestampUnix timestamp (seconds) of when the delivery was sent.
X-Webhook-EventThe event type (e.g., meeting_request.booked).
Content-TypeAlways application/json.

The X-Webhook-Signature header uses this format:

t=1706000000,v1=5257a869e7ecebeda32affa62cdca3fa51cad7e77a0e56ff536d0ce8e108d8bd
  • t — The same timestamp as X-Webhook-Timestamp.
  • v1 — The HMAC-SHA256 hex digest of the signed payload.
  1. Extract the timestamp (t) and signature (v1) from the X-Webhook-Signature header.
  2. Build the signed payload by concatenating the timestamp, a period (.), and the raw request body: {timestamp}.{body}
  3. Compute the expected signature using HMAC-SHA256 with your webhook endpoint’s signing secret.
  4. Compare your computed signature to the v1 value. Use a constant-time comparison to prevent timing attacks.
def verify_webhook(request, secret)
signature_header = request.headers["X-Webhook-Signature"]
timestamp = signature_header[/t=(\d+)/, 1]
received_sig = signature_header[/v1=(\w+)/, 1]
signed_payload = "#{timestamp}.#{request.body.read}"
expected_sig = OpenSSL::HMAC.hexdigest("SHA256", secret, signed_payload)
ActiveSupport::SecurityUtils.secure_compare(expected_sig, received_sig)
end
const crypto = require("crypto");
function verifyWebhook(headers, body, secret) {
const signature = headers["x-webhook-signature"];
const [tPart, vPart] = signature.split(",");
const timestamp = tPart.replace("t=", "");
const receivedSig = vPart.replace("v1=", "");
const signedPayload = `${timestamp}.${body}`;
const expectedSig = crypto
.createHmac("sha256", secret)
.update(signedPayload)
.digest("hex");
return crypto.timingSafeEqual(
Buffer.from(expectedSig),
Buffer.from(receivedSig)
);
}
import hmac
import hashlib
def verify_webhook(headers, body, secret):
signature = headers["X-Webhook-Signature"]
parts = dict(p.split("=", 1) for p in signature.split(","))
timestamp = parts["t"]
received_sig = parts["v1"]
signed_payload = f"{timestamp}.{body}"
expected_sig = hmac.new(
secret.encode(), signed_payload.encode(), hashlib.sha256
).hexdigest()
return hmac.compare_digest(expected_sig, received_sig)

SkipUp retries failed deliveries up to 8 times using polynomial backoff. The approximate retry schedule is:

AttemptDelay
1Immediate
2~3 seconds
3~18 seconds
4~83 seconds
5~4 minutes
6~10 minutes
7~22 minutes
8~40 minutes

A delivery is considered successful when your server responds with a 2xx status code. Any other status code (or a connection error) is treated as a failure.

Deliveries time out after 10 seconds. Make sure your webhook handler responds quickly. If you need to do heavy processing, acknowledge the webhook immediately and process the payload asynchronously.

If a webhook endpoint accumulates 10 or more failed deliveries within a 24-hour period, SkipUp automatically disables it. When this happens:

  • The enabled field is set to false.
  • The disabled_at timestamp is set.
  • The disabled_reason is set to too_many_failures.

To re-enable the endpoint, fix the issue with your server and then update the endpoint:

Terminal window
curl -X PATCH https://api.skipup.ai/api/v1/webhook_endpoints/we_01HQ... \
-H "Authorization: Bearer $SKIPUP_API_KEY" \
-H "Content-Type: application/json" \
-d '{"enabled": true}'