Webhooks

Real-time notifications, delivered to you.

Register HTTP endpoints, filter by event type, verify HMAC signatures, and build reactive integrations that respond the moment something happens.

Concepts

  • Endpoint -- A URL you register to receive event notifications.
  • Delivery -- A single attempt to send an event payload to your endpoint.
  • Secret -- An HMAC signing key used to verify that a delivery came from wrk!ng.

API endpoints

All webhook endpoints require authentication with a Bearer token.

MethodPathDescription
POST/api/webhooksCreate an endpoint (returns signing secret once)
GET/api/webhooksList endpoints for your organization
GET/api/webhooks/:idGet endpoint details with recent deliveries
PATCH/api/webhooks/:idUpdate URL, event types, or active status
DELETE/api/webhooks/:idSoft-delete an endpoint
POST/api/webhooks/:id/testFire a test event to the endpoint
GET/api/webhooks/:id/deliveriesList deliveries (paginated)
POST/api/webhooks/:id/deliveries/:did/retryManually retry a failed delivery

Creating a webhook

Register a new endpoint by posting the target URL and the event types you want to receive:

curl -X POST https://api.wrk.ing/api/webhooks \
  -H "Authorization: Bearer $TOKEN" \
  -H "Content-Type: application/json" \
  -d '{
    "url": "https://your-server.com/wrking-webhook",
    "event_types": ["task.post_create", "employee.post_update"],
    "description": "Sync tasks and employee changes"
  }'
const res = await fetch("https://api.wrk.ing/api/webhooks", {
  method: "POST",
  headers: {
    Authorization: `Bearer ${token}`,
    "Content-Type": "application/json",
  },
  body: JSON.stringify({
    url: "https://your-server.com/wrking-webhook",
    event_types: ["task.post_create", "employee.post_update"],
    description: "Sync tasks and employee changes",
  }),
});
const webhook = await res.json();
res = requests.post(
    "https://api.wrk.ing/api/webhooks",
    headers={"Authorization": f"Bearer {token}"},
    json={
        "url": "https://your-server.com/wrking-webhook",
        "event_types": ["task.post_create", "employee.post_update"],
        "description": "Sync tasks and employee changes",
    },
)
webhook = res.json()

Copy your secret immediately

The response includes a secret field that is only returned once. You need it to verify delivery signatures -- store it securely right away.

{
  "id": 12,
  "url": "https://your-server.com/wrking-webhook",
  "secret": "whsec_7f3a9b...",
  "event_types": ["task.post_create", "employee.post_update"],
  "description": "Sync tasks and employee changes",
  "is_active": true
}

Event filtering

The event_types array controls which events are delivered to your endpoint. Pass an empty array (or omit the field) to subscribe to all events.

Event types follow the {aggregate_type}.{phase} naming pattern:

  • task.post_create -- a task was created
  • task.post_update -- a task was updated
  • employee.post_update -- an employee record was changed
  • workflow.completed -- a workflow run finished
  • feedback.post_create -- new feedback was submitted
  • survey.post_create -- a survey was created

See the Events guide for the full list of lifecycle phases and event types.

Delivery format

Each delivery is an HTTP POST to your endpoint URL.

Headers

HeaderDescription
Content-Typeapplication/json
X-Wrking-SignatureHMAC-SHA256 hex digest of the raw request body
X-Wrking-EventThe event type string (e.g. task.post_create)
X-Wrking-Delivery-IdUnique identifier for this delivery attempt

Body

The body is a JSON object containing the full event payload:

{
  "id": 8401,
  "org_id": 1,
  "aggregate_type": "task",
  "aggregate_id": 234,
  "event_type": "task.post_create",
  "payload": {
    "org_id": 1,
    "actor_id": 42,
    "record_id": 234,
    "record_type": "task",
    "record_name": "Update Q3 roadmap",
    "action": "post_create",
    "timestamp": "2026-04-12T14:30:00.000Z"
  },
  "occurred_at": "2026-04-12T14:30:00.000Z"
}

Signature verification

Always verify the X-Wrking-Signature header before processing a delivery. This confirms the payload was sent by wrk!ng and has not been tampered with.

The signature is a hex-encoded HMAC-SHA256 digest of the raw request body, using your endpoint's secret as the key.

import crypto from "node:crypto";

function verifySignature(rawBody, secret, signature) {
  const expected = crypto
    .createHmac("sha256", secret)
    .update(rawBody)
    .digest("hex");
  return crypto.timingSafeEqual(
    Buffer.from(signature),
    Buffer.from(expected),
  );
}

// In your request handler:
const isValid = verifySignature(
  req.body,           // raw string, not parsed JSON
  process.env.WEBHOOK_SECRET,
  req.headers["x-wrking-signature"],
);
if (!isValid) {
  res.status(401).send("Invalid signature");
  return;
}
import hashlib
import hmac

def verify_signature(raw_body: bytes, secret: str, signature: str) -> bool:
    expected = hmac.new(
        secret.encode(),
        raw_body,
        hashlib.sha256,
    ).hexdigest()
    return hmac.compare_digest(expected, signature)
package main

import (
    "crypto/hmac"
    "crypto/sha256"
    "encoding/hex"
)

func verifySignature(body []byte, secret, signature string) bool {
    mac := hmac.New(sha256.New, []byte(secret))
    mac.Write(body)
    expected := hex.EncodeToString(mac.Sum(nil))
    return hmac.Equal([]byte(expected), []byte(signature))
}

Retry policy

Automatic retries with backoff

When a delivery fails, wrk!ng retries up to 6 times with exponential backoff. After all attempts are exhausted, you can trigger a manual retry through the API at any time.

AttemptDelay after failure
1Immediate
21 minute
35 minutes
430 minutes
52 hours
612 hours

Success means your endpoint returned an HTTP 2xx status code within 10 seconds.

Failure means any non-2xx response, a timeout (>10s), or a network error.

Testing

Use the test endpoint to send a webhook.test event to your registered URL without waiting for a real event:

curl -X POST https://api.wrk.ing/api/webhooks/12/test \
  -H "Authorization: Bearer $TOKEN"
await fetch("https://api.wrk.ing/api/webhooks/12/test", {
  method: "POST",
  headers: { Authorization: `Bearer ${token}` },
});
requests.post(
    "https://api.wrk.ing/api/webhooks/12/test",
    headers={"Authorization": f"Bearer {token}"},
)

This fires a delivery with event_type: "webhook.test" and a sample payload. Check your server logs to confirm it arrived and that signature verification passed.

Best practices

  • Respond with 2xx quickly. Do your processing asynchronously. If your handler takes more than 10 seconds, the delivery is marked as failed.
  • Verify signatures on every request. Never skip this step, even in development.
  • Use the delivery ID for idempotency. The X-Wrking-Delivery-Id header uniquely identifies each delivery. Store processed IDs to avoid handling the same event twice on retries.
  • Return 4xx for permanent failures. If a delivery payload is malformed or your system cannot process that event type, return a 4xx to prevent unnecessary retries.
  • Monitor your delivery log. Check the /api/webhooks/:id/deliveries endpoint or the admin UI to spot patterns in failed deliveries.