Connecting RenderBid to the partner program

The RenderBid site and the RenderBid CRM stay the source of truth. They tell this program three things: a customer paid, a customer went live, a customer canceled. This program enrolls every paying customer as a partner, moves each referral from received to signed to live, emails the partner at every step from a renderbid.com address, and sends a signed webhook back so the CRM can tag the contact.

1. The customer endpoint (checkout, go live, cancel)

POST https://partners.renderbid.com/api/renderbid/customer

Signed with the shared secret RENDERBID_SITE_SECRET, the same value on the RenderBid site and here. Header X-RenderBid-Signature: t=<unix seconds>,v1=<hex>, where the hex is HMAC-SHA256 of <t>.<raw body>. A signature older than five minutes is refused.

Use the same customer.id every time for the same contractor, and the same id the site sent asexternal_id on referral.created. The CRM contact id is the safest choice. event_id makes every call safe to retry.

FieldTypeRequiredNotes
event"customer.paid" | "customer.live" | "customer.canceled"yes
event_idstringyesUnique per event, for example the Stripe event id. A retry with the same id changes nothing.
occurred_atstring (date-time)yes
customerobjectyes
referral_codeobjectnoThe partner code saved from the ?ref= parameter when this customer arrived through a partner link.
amount_centsintegernocustomer.paid only: the first charge in cents.
owner_emailstring (email)noThe RenderBid team member who owns the deal. Defaults to PROGRAM_DEFAULT_REP_EMAIL.
enrollbooleannocustomer.paid only: enroll the payer as a partner. Default true.
welcome_delay_daysintegernocustomer.paid only: hold the welcome email this many days. Default 3.
FieldTypeRequiredNotes
customer.idstringyesThe id RenderBid uses for this contractor everywhere. Use the same id the site sent as external_id on referral.created; the CRM contact id is recommended.
customer.emailstring (email)yes
customer.first_namestringyes
customer.last_namestringyes
customer.companystringno
customer.phonestringno
{
  "event": "customer.paid",
  "event_id": "evt_1QxYz2AbCdEf",
  "occurred_at": "2026-10-12T16:20:00Z",
  "customer": {
    "id": "cus_Q9m3Fz7T",
    "email": "jane@smithroofing.example",
    "first_name": "Jane",
    "last_name": "Smith",
    "company": "Smith Roofing and Siding",
    "phone": "+17205550142"
  },
  "referral_code": "K7Q2M9AB",
  "amount_cents": 49700,
  "owner_email": "brett@renderbid.com",
  "enroll": true,
  "welcome_delay_days": 3
}

Answer (200):

{
  "ok": true,
  "partner": { "id": "<uuid>", "partner_code": "Z4R8T2QW", "referral_link": "https://partners.renderbid.com/r/Z4R8T2QW", "join_link": "https://partners.renderbid.com/join/Z4R8T2QW", "created": true },
  "queued_events": [
    { "event_type": "referral.created", "event_id": "<uuid>", "status": "accepted" },
    { "event_type": "sale.resulted", "event_id": "<uuid>", "status": "accepted" }
  ]
}

400 names the first bad field, 401 means the signature is missing, wrong, or stale, 422 means no team member is set up yet. Retry a 500 with the same body after a minute.

// Node, on the RenderBid site
import { createHmac } from "node:crypto";

const body = JSON.stringify(payload);
const t = Math.floor(Date.now() / 1000);
const v1 = createHmac("sha256", process.env.RENDERBID_SITE_SECRET).update(`${t}.${body}`).digest("hex");

await fetch("https://partners.renderbid.com/api/renderbid/customer", {
  method: "POST",
  headers: { "Content-Type": "application/json", "X-RenderBid-Signature": `t=${t},v1=${v1}` },
  body,
});

2. The events API (referral leads and corrections)

When a contractor fills in the demo form on renderbid.com with a saved ref code, sendreferral.created here so the partner hears "your referral is in" right away. The other events are available for corrections; the customer endpoint above covers the normal path.

POST https://partners.renderbid.com/api/v1/events

Headers: Authorization: Bearer rbk_... (from the dashboard API keys page) and Content-Type: application/json. Every event carries anexternal_event_id that is stored once: 202 means stored, 200 means already stored. Machine-readable contract: /api/v1/openapi.json.

Envelope, the same for every event

FieldTypeRequiredNotes
event_type"contact.created"yes
external_event_idstringyesUnique per event in your system. Retries with the same id are stored once.
occurred_atstring (date-time)yesWhen it happened, ISO 8601 with timezone.
rep_emailstring (email)yesThe RenderBid team member who owns the deal, for example brett@renderbid.com. Must match a rep on the dashboard.

contact.created

A contractor record exists in RenderBid's system. No referral is attached; accepted and logged only.

FieldTypeRequiredNotes
data.external_idstringyesRenderBid's id for this contractor (the CRM contact id or the Stripe customer id). Required on every event.
data.first_namestringyes
data.last_namestringyes
data.companystringnoThe contractor's business name.
data.emailstring (email)no
data.phonestringnoE.164, for example +15125550142.
data.address1stringno
data.citystringno
data.statestringno
data.postal_codestringno
curl -X POST https://partners.renderbid.com/api/v1/events \
  -H "Authorization: Bearer rbk_YOUR_KEY" \
  -H "Content-Type: application/json" \
  -d '{
  "event_type": "contact.created",
  "external_event_id": "evt-20260904-000120",
  "occurred_at": "2026-09-04T14:30:00Z",
  "rep_email": "brett@renderbid.com",
  "data": {
    "external_id": "cus_R2x7Lk0Q",
    "first_name": "Alex",
    "last_name": "Rivera",
    "company": "Rivera Exteriors",
    "email": "alex@example.com",
    "phone": "+17205550100"
  }
}'

referral.created

A contractor came in through a partner's link or a partner's introduction. Include the partner's code or email.

FieldTypeRequiredNotes
data.external_idstringyesRenderBid's id for this contractor (the CRM contact id or the Stripe customer id). Required on every event.
data.first_namestringyes
data.last_namestringyes
data.companystringnoThe contractor's business name.
data.emailstring (email)no
data.phonestringnoE.164, for example +15125550142.
data.address1stringno
data.citystringno
data.statestringno
data.postal_codestringno
data.partner_codestringnoThe referring partner's code. A partner link forwards to renderbid.com with the code as the ref query parameter; report it here so the partner is credited.
data.partner_emailstring (email)noThe referring partner's email, when the code is unknown.
curl -X POST https://partners.renderbid.com/api/v1/events \
  -H "Authorization: Bearer rbk_YOUR_KEY" \
  -H "Content-Type: application/json" \
  -d '{
  "event_type": "referral.created",
  "external_event_id": "evt-20260904-000123",
  "occurred_at": "2026-09-04T15:04:05Z",
  "rep_email": "brett@renderbid.com",
  "data": {
    "external_id": "cus_Q9m3Fz7T",
    "first_name": "Jane",
    "last_name": "Smith",
    "company": "Smith Roofing and Siding",
    "email": "jane@example.com",
    "phone": "+17205550142",
    "partner_code": "K7Q2M9AB"
  }
}'

sale.resulted

The referred contractor paid for RenderBid. The referral moves to Signed.

FieldTypeRequiredNotes
data.external_idstringyes
data.sale_amount_centsintegeryesThe first charge, in whole cents. 49700 is $497.00.
data.sale_datestring (date)yes
data.sale_type"new" | "upgrade" | "referral"noOptional. Defaults to referral.
curl -X POST https://partners.renderbid.com/api/v1/events \
  -H "Authorization: Bearer rbk_YOUR_KEY" \
  -H "Content-Type: application/json" \
  -d '{
  "event_type": "sale.resulted",
  "external_event_id": "evt-20260910-000301",
  "occurred_at": "2026-09-10T20:15:00Z",
  "rep_email": "brett@renderbid.com",
  "data": {
    "external_id": "cus_Q9m3Fz7T",
    "sale_amount_cents": 49700,
    "sale_date": "2026-09-10",
    "sale_type": "referral"
  }
}'

sale.canceled

The referred contractor canceled before the reward was paid. The referral moves to Canceled.

FieldTypeRequiredNotes
data.external_idstringyes
data.canceled_datestring (date)yes
curl -X POST https://partners.renderbid.com/api/v1/events \
  -H "Authorization: Bearer rbk_YOUR_KEY" \
  -H "Content-Type: application/json" \
  -d '{
  "event_type": "sale.canceled",
  "external_event_id": "evt-20260912-000340",
  "occurred_at": "2026-09-12T16:00:00Z",
  "rep_email": "brett@renderbid.com",
  "data": {
    "external_id": "cus_Q9m3Fz7T",
    "canceled_date": "2026-09-12"
  }
}'

appointment.scheduled

A demo or onboarding call was booked. Logged on the referral; the status does not change.

FieldTypeRequiredNotes
data.external_idstringyes
data.appointment_type"demo" | "onboarding" | "other"yes
data.scheduled_forstring (date-time)yes
curl -X POST https://partners.renderbid.com/api/v1/events \
  -H "Authorization: Bearer rbk_YOUR_KEY" \
  -H "Content-Type: application/json" \
  -d '{
  "event_type": "appointment.scheduled",
  "external_event_id": "evt-20260905-000150",
  "occurred_at": "2026-09-05T13:00:00Z",
  "rep_email": "brett@renderbid.com",
  "data": {
    "external_id": "cus_Q9m3Fz7T",
    "appointment_type": "demo",
    "scheduled_for": "2026-09-09T10:00:00-06:00"
  }
}'

install.scheduled

A go-live date was set. Logged on the referral; the status does not change.

FieldTypeRequiredNotes
data.external_idstringyes
data.install_datestring (date)yes
curl -X POST https://partners.renderbid.com/api/v1/events \
  -H "Authorization: Bearer rbk_YOUR_KEY" \
  -H "Content-Type: application/json" \
  -d '{
  "event_type": "install.scheduled",
  "external_event_id": "evt-20260915-000410",
  "occurred_at": "2026-09-15T18:00:00Z",
  "rep_email": "brett@renderbid.com",
  "data": {
    "external_id": "cus_Q9m3Fz7T",
    "install_date": "2026-10-02"
  }
}'

install.completed

The referred contractor went live on RenderBid. The referral moves to Live and the holding period starts.

FieldTypeRequiredNotes
data.external_idstringyes
data.completed_datestring (date)yes
curl -X POST https://partners.renderbid.com/api/v1/events \
  -H "Authorization: Bearer rbk_YOUR_KEY" \
  -H "Content-Type: application/json" \
  -d '{
  "event_type": "install.completed",
  "external_event_id": "evt-20261002-000512",
  "occurred_at": "2026-10-02T21:30:00Z",
  "rep_email": "brett@renderbid.com",
  "data": {
    "external_id": "cus_Q9m3Fz7T",
    "completed_date": "2026-10-02"
  }
}'

referral.paid

RenderBid paid the partner's reward for this contractor. The referral moves to Paid.

FieldTypeRequiredNotes
data.external_idstringyes
data.paid_datestring (date)yes
data.amount_centsintegerno
curl -X POST https://partners.renderbid.com/api/v1/events \
  -H "Authorization: Bearer rbk_YOUR_KEY" \
  -H "Content-Type: application/json" \
  -d '{
  "event_type": "referral.paid",
  "external_event_id": "evt-20261015-000600",
  "occurred_at": "2026-10-15T15:00:00Z",
  "rep_email": "brett@renderbid.com",
  "data": {
    "external_id": "cus_Q9m3Fz7T",
    "paid_date": "2026-10-15",
    "amount_cents": 50000
  }
}'

3. Webhooks to the RenderBid CRM

Add the CRM's address on the dashboard Webhooks page and pick events. Each delivery is a POST within a minute:

Flat fields for the CRM

Every body starts with flat, top-level fields that a no-code tool can map directly, so a workflow never has to reach into a nested object. The nested partner, referral, and payout objects are still included, unchanged, for receivers that already read them.

FieldOnMeaning
eventevery eventThe event name, the same as event_type.
occurred_atevery eventWhen it happened, ISO 8601 in UTC.
partner_emailevery eventThe partner's email. Use it to find or create the CRM contact.
partner_first_nameevery eventFirst name.
partner_last_nameevery eventLast name (only the initial for partners added before October 8, 2026).
partner_companyevery eventCompany name, or null.
partner_phoneevery eventPhone in +1 format, or null.
partner_codeevery eventThe partner's code, as in their link.
partner_kindevery eventcustomer or preferred.
partner_tierevery eventrookie, bronze, silver, gold, or platinum, by all-time paid referrals.
partner_paid_countevery eventAll-time paid referrals.
partner_statusevery eventinvited (no portal login yet), active, or unsubscribed.
partner_w9_statusevery eventnot_requested, requested, or received.
referral_linkevery eventThe partner's personal referral link.
portal_linkevery eventThe partner portal address.
owner_emailevery eventThe RenderBid team member who owns the partner (or, on referral events, the referral).
referral_statusreferral and payout eventsReceived, Signed, Live, Paid, or Canceled.
previous_referral_statusreferral.status_changed, referral.manual_moveThe status before the move, in the same words.
referred_companyreferral and payout eventsThe referred contractor's company, or null.
referred_namereferral and payout eventsThe referred contractor as the partner knows them, "Jane S."
reward_centsreferral and payout eventsThe reward in cents: the amount paid once paid, the expected amount before that, null when canceled.
reward_dollarsreferral and payout eventsThe same amount formatted, "$500.00".
POST <your webhook url>
X-RenderBid-Webhook-Timestamp: 1788000000
X-RenderBid-Webhook-Signature: sha256=<hex HMAC-SHA256 over "<timestamp>.<raw body>" using the webhook secret>
Content-Type: application/json

referral.status_changed

{
  "id": "<delivery uuid>",
  "event": "referral.status_changed",
  "occurred_at": "2026-10-12T16:21:00Z",
  "partner_id": "6f1d2c3b-4a59-4e8f-9b7a-0c1d2e3f4a5b",
  "partner_email": "mike@example.com",
  "partner_first_name": "Mike",
  "partner_last_name": "Torres",
  "partner_company": "Torres Exteriors",
  "partner_phone": "+17205550188",
  "partner_code": "K7Q2M9AB",
  "partner_kind": "customer",
  "partner_tier": "rookie",
  "partner_paid_count": 0,
  "partner_status": "active",
  "partner_w9_status": "not_requested",
  "referral_link": "https://partners.renderbid.com/r/K7Q2M9AB",
  "portal_link": "https://partners.renderbid.com/portal",
  "owner_email": "brett@renderbid.com",
  "referral_id": "0b8e7d6c-5a4f-4c3b-8a29-1f0e9d8c7b6a",
  "referral_external_id": "cus_Q9m3Fz7T",
  "referral_status": "Signed",
  "referred_company": "Smith Roofing and Siding",
  "referred_name": "Jane S.",
  "reward_cents": 50000,
  "reward_dollars": "$500.00",
  "previous_referral_status": "Received",
  "event_type": "referral.status_changed",
  "office_slug": "renderbid",
  "referral": {
    "id": "0b8e7d6c-5a4f-4c3b-8a29-1f0e9d8c7b6a",
    "external_id": "cus_Q9m3Fz7T",
    "status": "sold",
    "previous_status": "received",
    "status_label": "Signed",
    "partner_code": "K7Q2M9AB",
    "partner_email": "mike@example.com",
    "rep_email": "brett@renderbid.com"
  }
}

payout.created

{
  "id": "<delivery uuid>",
  "event": "payout.created",
  "occurred_at": "2026-12-14T17:05:00Z",
  "partner_id": "6f1d2c3b-4a59-4e8f-9b7a-0c1d2e3f4a5b",
  "partner_email": "mike@example.com",
  "partner_first_name": "Mike",
  "partner_last_name": "Torres",
  "partner_company": "Torres Exteriors",
  "partner_phone": "+17205550188",
  "partner_code": "K7Q2M9AB",
  "partner_kind": "customer",
  "partner_tier": "bronze",
  "partner_paid_count": 1,
  "partner_status": "active",
  "partner_w9_status": "received",
  "referral_link": "https://partners.renderbid.com/r/K7Q2M9AB",
  "portal_link": "https://partners.renderbid.com/portal",
  "owner_email": "brett@renderbid.com",
  "referral_id": "0b8e7d6c-5a4f-4c3b-8a29-1f0e9d8c7b6a",
  "referral_external_id": "cus_Q9m3Fz7T",
  "referral_status": "Paid",
  "referred_company": "Smith Roofing and Siding",
  "referred_name": "Jane S.",
  "reward_cents": 50000,
  "reward_dollars": "$500.00",
  "payout_sequence_number": 1,
  "event_type": "payout.created",
  "office_slug": "renderbid",
  "referral": {
    "id": "0b8e7d6c-5a4f-4c3b-8a29-1f0e9d8c7b6a",
    "external_id": "cus_Q9m3Fz7T",
    "status": "paid",
    "previous_status": "installed",
    "status_label": "Paid",
    "partner_code": "K7Q2M9AB",
    "partner_email": "mike@example.com",
    "rep_email": "brett@renderbid.com"
  },
  "payout": {
    "amount_cents": 50000,
    "sequence_number": 1,
    "marked_at": "2026-12-14T17:05:00Z"
  }
}

partner.enrolled

{
  "id": "<delivery uuid>",
  "event": "partner.enrolled",
  "occurred_at": "2026-10-09T15:00:00Z",
  "partner_id": "6f1d2c3b-4a59-4e8f-9b7a-0c1d2e3f4a5b",
  "partner_email": "mike@example.com",
  "partner_first_name": "Mike",
  "partner_last_name": "Torres",
  "partner_company": "Torres Exteriors",
  "partner_phone": "+17205550188",
  "partner_code": "K7Q2M9AB",
  "partner_kind": "customer",
  "partner_tier": "rookie",
  "partner_paid_count": 0,
  "partner_status": "invited",
  "partner_w9_status": "not_requested",
  "referral_link": "https://partners.renderbid.com/r/K7Q2M9AB",
  "portal_link": "https://partners.renderbid.com/portal",
  "owner_email": "brett@renderbid.com",
  "event_type": "partner.enrolled",
  "source": "checkout",
  "join_link": "https://partners.renderbid.com/join/K7Q2M9AB",
  "partner": {
    "id": "6f1d2c3b-4a59-4e8f-9b7a-0c1d2e3f4a5b",
    "partner_code": "K7Q2M9AB",
    "kind": "customer",
    "first_name": "Mike",
    "last_initial": "T",
    "company_name": "Torres Exteriors",
    "email": "mike@example.com",
    "joined": false,
    "referral_link": "https://partners.renderbid.com/r/K7Q2M9AB",
    "join_link": "https://partners.renderbid.com/join/K7Q2M9AB",
    "portal_link": "https://partners.renderbid.com/portal"
  }
}

Verifying a webhook

Read the raw body as text. Join the timestamp header, a period, and the raw body. HMAC-SHA256 it with the webhook secret shown once when the webhook was added, hex encode, prefix sha256=, and compare in constant time. Reject anything older than five minutes. Answer any 2xx within ten seconds; anything else is retried after 1, 5, 15, and 30 minutes. Use the delivery id to ignore repeats.

import { createHmac, timingSafeEqual } from "node:crypto";

function verify(rawBody, timestamp, signature, secret) {
  const expected = "sha256=" + createHmac("sha256", secret).update(timestamp + "." + rawBody).digest("hex");
  return expected.length === signature.length && timingSafeEqual(Buffer.from(expected), Buffer.from(signature));
}

Questions

Write to Support@RenderBid.com and include the event id from the response.