Skip to content

Webhooks

Instead of polling the API, let Rigyx call you. Subscribe a URL to the events you care about and get a signed POST the moment each one happens.

Subscribe

Create a subscription with POST /webhooks — a URL plus the events to send it. The URL must be https:// in production. The response includes a signing secret, returned once — store it; you will need it to verify every delivery.

POST /v1/webhooks
curl -X POST https://rigyx.com/api/public/v1/webhooks \
  -H "Authorization: Bearer jmt_..." \
  -H "Content-Type: application/json" \
  -d '{
    "name": "Pipeline sync",
    "url": "https://your-server.com/hooks/rigyx",
    "events": ["lead.qualified", "campaign.completed"]
  }'
201 Created
{
  "id": "5b2e...-uuid",
  "url": "https://your-server.com/hooks/rigyx",
  "events": ["lead.qualified", "campaign.completed"],
  "secret": "whsec_a1b2c3...   ← shown once, store it now",
  "isActive": true
}

The event catalogue

Subscribe to any of these event types. An unknown type is rejected at subscribe time.

Leads
lead.createdlead.qualifiedlead.converted
Contacts
contact.createdcontact.updated
Campaigns
campaign.createdcampaign.sentcampaign.completed
Email
email.sentemail.openedemail.clickedemail.repliedemail.received
Documents
document.draftdocument.approveddocument.sent
Invoices
invoice.createdinvoice.paidinvoice.overdue
Tasks
task.createdtask.completedtask.failed
Reviews
review.discoveredreview.approvedreview.posted
Ad campaigns
adcamp.approvedadcamp.launchedadcamp.metrics_updated
Agents
agent.task_startedagent.task_completedagent.handoff_sent

The payload

Every delivery is a POST with the same JSON envelope. The event-specific fields live under data.

Request body
{
  "id": "e9f1...-uuid",
  "type": "lead.qualified",
  "business_id": "5b2e...-uuid",
  "created_at": "2026-05-22T14:22:51.512Z",
  "data": {
    "leadId": "a1b2...-uuid",
    "companyName": "Acme Ltd",
    "source": "sales_agent"
  }
}

Alongside the body, every delivery carries these headers:

Delivery headers
Content-Type:            application/json
User-Agent:              Rigyx-Webhooks/1.0
X-Rigyx-Signature:   sha256=<hex>
X-Rigyx-Timestamp:   <unix seconds>
X-Rigyx-Delivery-Id: <uuid>
X-Rigyx-Event:       <event type>

Verify the signature

Always verify before trusting a payload. The signature is an HMAC-SHA256, keyed with your subscription secret, computed over {timestamp}.{raw_body} — the timestamp, a dot, then the exact bytes of the request body.

Use the raw body
Compute the HMAC over the raw request body, before any JSON parse-and-re-serialize. Re-serializing can reorder keys or change whitespace and the signature will not match. Also reject any delivery whose timestamp is more than 5 minutes old — that blocks replay attacks.
Node.js
import crypto from 'crypto';

// Verify an inbound Rigyx webhook. Returns true if authentic.
function verifyWebhook(headers, rawBody, secret) {
  const signature = headers['x-rigyx-signature'] || ''; // "sha256=<hex>"
  const timestamp = headers['x-rigyx-timestamp'] || ''; // unix seconds

  // Replay protection — reject anything older than 5 minutes.
  const age = Math.floor(Date.now() / 1000) - parseInt(timestamp, 10);
  if (Number.isNaN(age) || age > 300) return false;

  const expected = 'sha256=' + crypto
    .createHmac('sha256', secret)
    .update(timestamp + '.' + rawBody)
    .digest('hex');

  const a = Buffer.from(signature);
  const b = Buffer.from(expected);
  return a.length === b.length && crypto.timingSafeEqual(a, b);
}
Python
import hmac, hashlib, time

def verify_webhook(headers, raw_body: bytes, secret: str) -> bool:
    signature = headers.get("X-Rigyx-Signature", "")  # "sha256=<hex>"
    timestamp = headers.get("X-Rigyx-Timestamp", "")  # unix seconds

    # Replay protection — reject anything older than 5 minutes.
    try:
        age = int(time.time()) - int(timestamp)
    except ValueError:
        return False
    if age > 300:
        return False

    signed = timestamp.encode() + b"." + raw_body
    expected = "sha256=" + hmac.new(secret.encode(), signed, hashlib.sha256).hexdigest()
    return hmac.compare_digest(signature, expected)

Retries & failure handling

The first delivery is attempted immediately. If your endpoint does not answer with a 2xx, it is retried with growing backoff:

Retry schedule
attempt 1   immediate
attempt 2   after  2 minutes
attempt 3   after 10 minutes
attempt 4   after  1 hour
attempt 5   after  6 hours
attempt 6   after 24 hours
            → still failing? delivery marked "abandoned"

Each delivery is given six attempts in total. Separately, if a subscription racks up 10 consecutive failed deliveries, it is automatically disabled and the business owner is notified — so a permanently broken endpoint does not retry forever. Re-enable it from the portal once the endpoint is fixed.

Respond fast, work later
Return 2xx as soon as you have stored the event, then do the real work asynchronously. Deliveries time out after 10 seconds — if your handler is slow, the delivery counts as failed.

What is never sent

Before signing, the dispatcher strips sensitive fields from every payload — API keys, secrets, tokens, passwords, raw LLM prompts and responses, and internal reasoning. A webhook payload only ever contains business data safe to leave your perimeter.

Catching up after an outage

If your endpoint is down long enough for deliveries to be abandoned, backfill from the events log rather than losing them. Poll GET /events — it is cursor-paginated — and page back to your last-known event.

curl
curl -H "Authorization: Bearer jmt_..." \
  "https://rigyx.com/api/public/v1/events?type=lead.qualified&limit=200"
That is the API
You now have the whole surface — auth, conventions, endpoints and webhooks. For the product side, browse the documentation home.