Developers/Webhooks
Outgoing Webhooks

Webhooks

Swake pushes real-time HTTP POST notifications to any URL you register whenever feedback lifecycle events occur. Use webhooks to build custom automations, connect to Zapier or Make, or integrate with any system that accepts HTTP.

Overview

Every webhook delivery is an HTTP POST to your endpoint with a JSON body, four signature headers, and a 10-second timeout. Deliveries are retried up to five times with exponential backoff on failure.

Signing secret — shown once at creation time and never again. Store it securely in an environment variable. You can rotate it at any time from Settings → Webhooks.


Plan Availability

PlanEndpoints per project
Free0 — not available
Pro2
Business10
EnterpriseUnlimited

Registering an Endpoint

Open Project → Settings → Webhooks and click Add endpoint. Fill in:

FieldNotes
URLMust be a public https:// address. HTTP, localhost, and private IP ranges are rejected.
DescriptionOptional human-readable label for the endpoint.
EventsOne or more events to subscribe to (see Event Catalog).

The signing secret is shown exactly once when the endpoint is created. Copy it immediately and store it in an environment variable — it cannot be retrieved again.


Payload Envelope

Every delivery POSTs a JSON body with this shape:

json
{
  "id": "evt_01HY...",
  "event": "feedback.created",
  "timestamp": "2026-03-25T10:00:00Z",
  "workspace_id": "01HY...",
  "project_id": "01HY...",
  "data": { }
}
FieldDescription
idUnique delivery ID (evt_<ULID>)
eventEvent name (see catalog below)
timestampISO-8601 UTC timestamp of when the event occurred
workspace_idYour workspace ULID
project_idProject ULID the event belongs to
dataEvent-specific payload (see each event below)

Event Catalog

EventTrigger
feedback.createdNew feedback submitted via SDK or Portal
feedback.status_changedStatus updated (open → in_progress → resolved → closed)
feedback.assignedAssignee changed
feedback.commentedNew team comment added
feedback.taggedTag added or removed
vote.castVote cast on a voting board item
vote.removedVote removed from a voting board item
survey.response_completedSurvey / NPS response submitted
roadmap.item_movedRoadmap item moved between columns
changelog.publishedChangelog entry published
test.pingManual test delivery from the portal

Example Payloads

feedback.created

json
{
  "id": "evt_01HY...",
  "event": "feedback.created",
  "timestamp": "2026-03-25T10:00:00Z",
  "workspace_id": "01HY...",
  "project_id": "01HY...",
  "data": {
    "id": "01HY...",
    "type": "bug",
    "title": "App crashes on login screen",
    "description": "When I tap the login button...",
    "status": "open",
    "priority": "medium",
    "created_at": "2026-03-25T10:00:00Z"
  }
}

feedback.status_changed

json
{
  "data": {
    "id": "01HY...",
    "old_status": "open",
    "new_status": "in_progress",
    "changed_by": "tm_01HY..."
  }
}

Signature Verification

Every delivery includes four headers for verification:

bash
X-Swake-Signature:   sha256=a1b2c3d4e5f6...
X-Swake-Timestamp:   1711353600
X-Swake-Event:       feedback.created
X-Swake-Delivery-Id: evt_01HY...

The signature is computed as HMAC-SHA256(secret, "{timestamp}.{rawBody}"). The payload is the Unix timestamp joined with the raw request body by a . separator.

Replay attack prevention: reject deliveries where abs(now − X-Swake-Timestamp) > 300 (5 minutes). Always use a timing-safe comparison function when comparing signatures.

javascript
const crypto = require('crypto');

function verifyWebhook(rawBody, headers, secret) {
  const timestamp = headers['x-swake-timestamp'];
  const receivedSig = headers['x-swake-signature'].replace('sha256=', '');

  // Reject stale deliveries (> 5 minutes)
  if (Math.abs(Date.now() / 1000 - Number(timestamp)) > 300) {
    throw new Error('Delivery timestamp too old');
  }

  const expected = crypto
    .createHmac('sha256', secret)
    .update(`${timestamp}.${rawBody}`)
    .digest('hex');

  if (!crypto.timingSafeEqual(Buffer.from(receivedSig), Buffer.from(expected))) {
    throw new Error('Invalid signature');
  }
}

Delivery Semantics

Swake considers any 2xx response a success. Non-2xx responses and network errors trigger the retry schedule below. Each attempt has a 10-second timeout.

Attempt 1ImmediateFirst delivery attempt as soon as the event fires
Attempt 230 secondsFirst retry after a short delay
Attempt 35 minutesSecond retry with increasing backoff
Attempt 430 minutesThird retry
Attempt 52 hoursFinal retry — no further attempts after this

Auto-disable: if an endpoint accumulates 50 consecutive failures across all event types, Swake automatically sets it to inactive and emails the workspace owner. Re-enable it from Settings → Webhooks once the issue is resolved.


Testing Webhooks

Click Test on any endpoint in the portal to send a test.ping delivery. The portal shows the HTTP status and response time of the test request.

json
{
  "id": "evt_test_01HY...",
  "event": "test.ping",
  "timestamp": "2026-03-25T10:00:00Z",
  "workspace_id": "01HY...",
  "project_id": "01HY...",
  "data": { "message": "Webhook is working!" }
}

SSRF Prevention

To protect against Server-Side Request Forgery, the following endpoint URLs are always rejected:

Rejected patternReason
http://, file://, etc.Non-HTTPS schemes
localhost, *.local, 0.0.0.0Local hostnames
127.x.x.x, 10.x.x.x, 172.16-31.x.xIPv4 private ranges
192.168.x.x, 169.254.x.xPrivate / link-local IPv4
::1IPv6 loopback

Delivery Log

Open Settings → Webhooks → {endpoint} → Deliveries to see a paginated log of all delivery attempts, including:

ColumnDescription
Event nameThe event type that triggered the delivery
HTTP statusResponse status code returned by your server
AttemptWhich attempt number (1–5)
Timestamp / next retryWhen it was delivered, or when the next retry is scheduled
Request payloadFull JSON body sent to your endpoint
Response bodyFirst 1 KB of your server's response (on failure)

Rotating the Signing Secret

If your secret is compromised:

StepAction
1Go to Settings → Webhooks and click Rotate secret on the affected endpoint.
2A new secret is generated. Copy it immediately — it is shown only once.
3Update your server to use the new secret as quickly as possible.
4Old secrets are immediately invalidated after rotation.

There is a brief window after rotation where old deliveries signed with the previous secret may arrive. Update your server as quickly as possible after rotating.


API Reference

All endpoints are under /v1/portal/projects/:projectId/webhooks and require a valid JWT session cookie.

MethodPathDescription
GET/projects/:projectId/webhooksList endpoints for a project
POST/projects/:projectId/webhooksCreate endpoint (secret shown once)
PATCH/projects/:projectId/webhooks/:idUpdate URL / description / events / active
POST/projects/:projectId/webhooks/:id/regenerate-secretRotate signing secret
POST/projects/:projectId/webhooks/:id/testSend test.ping delivery
DELETE/projects/:projectId/webhooks/:idDelete endpoint (cascades deliveries)
GET/projects/:projectId/webhooks/:id/deliveriesPaginated delivery log
GET/projects/:projectId/webhooks/:id/deliveries/:dIdFull delivery detail with payload