Webhooks

Subscribe to Partli events from your own backend. Signed, retried, audit-logged.

Partli sends webhook events to your registered endpoints whenever something important happens — a new application, a sale, a payout. Useful for syncing Partli state into your own database or triggering downstream actions.

Setup

Workspace sidebar → Developer → Webhooks. Click Create endpoint, paste your URL, pick the events you care about, save. We generate a signing secret you use to verify each delivery.

Available events

  • partner.applied — partner submitted an application
  • partner.approved — partner approved (manually or via auto-approve)
  • partner.rejected — partner application rejected
  • lead.created — attributed lead event
  • sale.created — attributed sale event
  • commission.created — pending commission row created
  • commission.approved — commission moved to approved (post-holding)
  • commission.paid — included in a successful payout
  • payout.sent — payout transfer to partner succeeded
  • payout.failed — payout transfer failed

Payload format

Each delivery includes two headers: x-partli-event with the event name, and x-partli-signature with the HMAC. The body is:

{
  "event": "sale.created",
  "timestamp": "2026-04-25T10:32:14.892Z",
  "data": {
    "saleId": "uuid-...",
    "partnerId": "uuid-...",
    "customerId": "uuid-...",
    "amountCents": 4900,
    "currency": "USD",
    "commissionId": "uuid-..."
  }
}

Verifying signatures

Every delivery includes x-partli-signature — an HMAC-SHA256 (hex) of the raw body using your endpoint's secret:

import crypto from "crypto";

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

// In your handler:
const valid = verifyPartliWebhook(rawBody, req.headers["x-partli-signature"], process.env.PARTLI_WEBHOOK_SECRET);
if (!valid) return res.status(400).end();

Retries

We retry non-2xx responses with exponential backoff: 1m, 5m, 30m, 2h, 6h, 24h. After 6 failed attempts the delivery is marked failed (visible in the webhook logs).

Idempotency on your side

Use a stable id from the data payload (e.g. saleId,commissionId) plus the event name as your idempotency key. We may retry — your handler should treat duplicate deliveries as no-ops.