Developers

Subscribe to
the events.

Pipe partner program events directly into your CRM, your audit trail, or your billing reconciliation. Every delivery is signed with HMAC SHA 256 and retried with backoff until you 200 us.

Event catalog

  • partner.approved

    Application moved to APPROVED. Referral link goes live.

  • partner.suspended

    Status changed to SUSPENDED with a reason.

  • tier.promoted

    Auto promotion fired on the nightly review.

  • commission.created

    New commission row, status PENDING, source amount and FX rate stamped.

  • commission.approved

    Hold elapsed, commission moved from PENDING to APPROVED.

  • commission.reversed

    Subscription cancelled inside the hold window. Pending commission reversed.

  • commission.paid

    Commission settled into a payout that was marked PAID.

  • payout.created

    Monthly aggregator bundled approved commissions into a PENDING payout.

  • payout.paid

    Wise transfer completed. Status PAID with reference.

  • payout.failed

    Transfer rejected by the rail. Reason on file.

Verify a delivery

Constant time HMAC compare.

Every payload arrives with an `x-rook-signature` header. Compare it against your secret in constant time. Fail closed on any mismatch.

// Express receiver

import express from 'express';
import crypto from 'node:crypto';

const SECRET = process.env.ROOK_FRIENDS_WEBHOOK_SECRET!;

app.post(
  '/webhooks/rook-friends',
  express.raw({ type: 'application/json' }),
  (req, res) => {
    const signature = req.headers['x-rook-signature'] as string;
    const body = req.body.toString();

    const expected = crypto
      .createHmac('sha256', SECRET)
      .update(body)
      .digest('hex');

    const ok = signature.length === expected.length &&
      crypto.timingSafeEqual(
        Buffer.from(signature),
        Buffer.from(expected),
      );

    if (!ok) {
      return res.status(401).json({ error: 'bad signature' });
    }

    const { event, payload } = JSON.parse(body);
    handle(event, payload);
    res.json({ ok: true });
  },
);
// Subscribe via the API

await fetch('https://api.rookfriends.com/api/v1/partner/webhooks', {
  method: 'POST',
  credentials: 'include',
  headers: {
    'Content-Type': 'application/json',
    'x-rook-csrf': csrfToken,
  },
  body: JSON.stringify({
    workspaceId: '<your workspace>',
    url: 'https://your.app/webhooks/rook-friends',
    events: ['commission.created', 'payout.paid'],
  }),
});

// Response includes a one-time secret you store
// for HMAC verification on the receiving side.

Subscribe

One POST. One secret.

Pick the events, point a URL at it, store the secret we hand back exactly once. Failed deliveries retry on backoff: one minute, five minutes, thirty minutes, one hour. Ten consecutive failures pauses the subscription so we do not hammer a dead endpoint.

Idempotency keys

Send Idempotency-Key on POST writes. Same key, same response, never run twice.

Audit immutable

Every state change writes a partner_events row. The table is append only at the database level.

Replay safe

External event IDs prevent duplicate commissions even when upstream redelivers.

Want to see this wired up?

Apply for access