Developer docs

Brisk API and CRM integrations

Every time a seller finishes your Brisk link, Brisk can send the lead to your CRM within seconds. Start with Zapier if you just want leads in Podio, GoHighLevel, Follow Up Boss, HubSpot or a Google Sheet; use webhooks or the REST API if you’re wiring up your own systems.

API version 2026-10-01Base URL https://app.briskoffers.com/api/v1

Get your leads into your CRM#

Every time a seller finishes your Brisk link, Brisk sends the lead to your CRM within seconds: their contact details, the property, its condition, the lowest price they’d accept, the offer they saw and the result. You don’t need a developer for the first two options.

Recommended · no code

Zapier

Connect Brisk to Podio, GoHighLevel, Follow Up Boss, HubSpot, Google Sheets and 6,000+ other apps.

Best for: getting leads into the tools you already use, today.

Webhooks

Brisk posts each lead as JSON to a URL you give it, signed so you know it came from Brisk.

Best for: CRMs and tools that accept an incoming webhook URL, or your own developer.

REST API

Pull leads and update deal status from your own systems with an API key.

Best for: custom dashboards, reporting and two-way sync.

Zapier in 4 steps#

  1. In Brisk, open Integrations → API keys and create a key. Name it “Zapier”. Copy it now: Brisk shows a key once.
  2. Connect Brisk in Zapier (the invite link is on your Integrations page) and paste the key when it asks. Zapier labels the connection with your account name.
  3. Pick the New Lead trigger. Choose a campaign, or leave it blank for all of them, and pick a Result if you only want certain leads.
  4. Add your CRM as the action and map the fields. The cheat sheet below shows which Brisk field goes where.

Ready-made recipes#

These are the templates on your Integrations page. Each one is a two- or three-step Zap.

Brisk → Podio

Every new Brisk lead becomes an item in your Podio Leads app, with the offer, the seller's minimum and the condition.

  1. Trigger: Brisk · New Lead (pick a campaign, or leave blank for all)
  2. Action: Podio · Create Item in your Leads app
  3. Map seller name, phone, email, address, offer shown, seller minimum and gate result

Brisk → HubSpot

Create or update a HubSpot contact for each seller and open a deal with the offer amount.

  1. Trigger: Brisk · New Lead
  2. Action: HubSpot · Create or Update Contact (match on email)
  3. Action: HubSpot · Create Deal: amount = offer shown, stage = your first stage

Brisk → Follow Up Boss

Send each seller to Follow Up Boss as a new lead so your team's action plans start right away.

  1. Trigger: Brisk · New Lead (filter: Qualified, Smart offer and Seller Financing, if you only want hot leads)
  2. Action: Follow Up Boss · Create Person (source: Brisk, tags: Tags)
  3. Map phone, email, address, and put the offer and minimum in the note

Brisk → GoHighLevel

Add each seller as a GoHighLevel contact and drop them into an opportunity pipeline. Seller financing leads get a "Seller Financing" tag; on an agent's campaign, the seller's pick arrives as "Wants to list" or "Wants cash".

  1. Trigger: Brisk · New Lead
  2. Action: GoHighLevel · Create/Update Contact (tags: brisk + Tags)
  3. Action: GoHighLevel · Create Opportunity: value = offer shown

Brisk → Google Sheets

Log every lead as a new row in a Google Sheet: a simple shared lead list.

  1. Trigger: Brisk · New Lead
  2. Action: Google Sheets · Create Spreadsheet Row
  3. One column per field: date, seller, phone, address, offer shown, minimum, gate result, lead link

Brisk → your CRM, for agents

Real estate agents: every seller lands in your CRM with the estimated sale price, the cash option (when shown) and which one they picked. A later pick arrives as an update.

  1. Trigger: Brisk · New Lead (your campaign)
  2. Action: your CRM · Create or Update Contact (match on email; tags: Tags)
  3. Map estimated sale price, cash offer, seller choice and gate result
  4. Second Zap: Brisk · Lead Updated (filter: update_reason is seller_choice) → update the contact's tags

Podio deal closed → Brisk status

When a deal is marked closed in Podio, mark the lead Closed in Brisk with the contract price, so your closing numbers stay right.

  1. Trigger: Podio · Item Updated (filter: status is Closed)
  2. Search: Brisk · Find Lead (by the seller's email or phone)
  3. Action: Brisk · Update Lead Status: Closed, with contract price and close date

Which leads get sent#

Every finished lead fires lead.created, whatever the result: Qualified, Smart offer, Cold, Manual review, all of them. To send only some, use the Result filter on the Zapier trigger, or the results filter on a webhook (Integrations → Webhooks, or gate_result on POST /hooks). The field is called gate_result and takes these values:

ValueLabelMeaning
qualifiedQualifiedThe seller's minimum was within your tolerance; they saw your full offer.
smart_offerSmart offerThe seller asked for less than your offer; Smart Offer showed them a number just above their minimum.
seller_financeSeller FinancingAbove your range but open to seller financing. No number was shown.
high_asking_priceHigh asking priceAgent campaigns only: the seller's price was above the estimated sale price plus tolerance. They still saw the estimate (and the cash option, when on).
coldColdThe seller's minimum was too far above your offer; they got a polite decline.
manual_reviewManual reviewSomething needs a person to look first (a flag, the floor, or no data). No number was shown.
disqualifiedDisqualifiedA flag action disqualified the home. The seller saw a decline.
pending_lookupNeeds lookup (account was out of lookups)The account was out of lookups when the seller came; contact details were saved and the lookup waits.
in_progressDidn't finishThe seller left contact details but didn't reach the end.

One exception: a lead that never reached the end of the form (in_progress) doesn’t fire lead.created. Those leads only show up in GET /leads, so you can still follow up.

Mapping cheat sheet#

The fields most CRMs need, and where they usually go. Every field is explained in the field reference.

Brisk fieldUsual CRM field
seller_first_name + seller_last_nameContact name (first and last)
seller_phonePhone
seller_emailEmail
address_line1 + city + state + zipProperty address
offer_shownDeal or opportunity value
seller_minimumAsking price
gate_resultLead status or pipeline stage
tagsTags
lead_urlNote or link field (opens the lead in Brisk)

Sending status back to Brisk#

Brisk’s report counts closed deals and contract prices, but only if it hears about them. The “Podio deal closed → Brisk status” recipe above does that: when you close a deal in your CRM, a Zap finds the lead in Brisk (by the seller’s email or phone) and marks it Closed with the contract price and close date. Any CRM that can trigger a Zap on a status change works the same way, and your own code can call PATCH /leads/{id}/status.

Quick start#

The base URL is https://app.briskoffers.com/api/v1. Every response is JSON.

Create a key

In Brisk, open Integrations → API keys. Keys look like brk_live_ followed by 32 letters and digits. Brisk shows a key once, stores only a fingerprint of it, and lets you revoke it at any time. A revoked key stops working immediately.

Your first call

Send the key as a bearer token. GET /me returns the account the key belongs to, so it’s the easiest way to test a connection.

curl "https://app.briskoffers.com/api/v1/me" \
  -H "Authorization: Bearer brk_live_XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX"
Response · 200
JSON
{
  "account_id": "00000000-0000-4000-8000-00000000000a",
  "account_name": "Your company",
  "handle": "your-company"
}

A key gives read access to every lead in the account and can change deal status. Keep it on a server, and never put it in a browser, a mobile app or a public repository.

Authentication, rate limits and errors#

Send your key on every request:

HTTP
Authorization: Bearer brk_live_XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX
  • A missing header, a key that doesn’t match the brk_live_ format, an unknown key or a revoked key all return 401 invalid_api_key.
  • The API needs an active plan or free access on the account; otherwise every call returns 403 plan_inactive.
  • Each key may make 120 requests per minute. Past that you get 429 rate_limited with a Retry-After header in seconds.
  • Every response carries Cache-Control: no-store.

Error format

Errors have a stable error code and a human-readable message:

JSON
{
  "error": "invalid_api_key",
  "message": "Send a valid API key: Authorization: Bearer brk_live_…"
}
CodeStatusWhen
bad_request400The body or a parameter is missing or invalid. The message says which.
invalid_api_key401No Authorization header, a malformed key, an unknown key, or a revoked key.
plan_inactive403The key is valid but the account has no active plan or free access.
not_found404The lead or subscription doesn't exist, or belongs to another account.
rate_limited429More than the per-minute limit for this key, or too many requests with a missing or wrong key from one connection. Wait Retry-After seconds.
server_error500Something failed on Brisk's side. Retry later.

Endpoints#

Lead responses use the same flat shape as webhooks (see the field reference), without the raw object. Leads from the widget preview in the portal are never returned.

GET/me#

The account the key belongs to. Use it to test a connection.

curl "https://app.briskoffers.com/api/v1/me" \
  -H "Authorization: Bearer brk_live_XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX"
Response · 200
JSON
{
  "account_id": "00000000-0000-4000-8000-00000000000a",
  "account_name": "Your company",
  "handle": "your-company"
}

Errors: 401, 403, 429.

GET/campaigns#

Your active (non-archived) campaigns, oldest first. Handy for a dropdown.

curl "https://app.briskoffers.com/api/v1/campaigns" \
  -H "Authorization: Bearer brk_live_XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX"
Response · 200
JSON
[
  {
    "id": "00000000-0000-4000-8000-00000000000b",
    "name": "Raleigh – Direct mail",
    "status": "live"
  }
]

Errors: 401, 403, 429.

GET/leads#

Your newest leads, or one lead found by email or phone.

NameTypeRequiredNotes
limitintegerOptionalHow many to return, newest first. Default 3, maximum 100.
emailstringOptionalFind leads with this email (case-insensitive, exact match).
phonestringOptionalFind leads with this phone number, in any format: the last 10 digits are compared. Fewer than 10 digits returns 400.
curl "https://app.briskoffers.com/api/v1/leads?limit=3" \
  -H "Authorization: Bearer brk_live_XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX"
Response · 200 · an array
JSON
[
  {
    "lead_id": "00000000-0000-4000-8000-000000000000",
    "seller_first_name": "TEST",
    "seller_last_name": "Seller",
    "seller_phone": "(555) 555-0100",
    "seller_email": "test.seller@example.com",
    "address_line1": "123 Test St",
    "unit": null,
    "city": "Raleigh",
    "state": "NC",
    "zip": "27610",
    "county": "Wake",
    "beds": 3,
    "baths": 2,
    "sqft": 1840,
    "sqft_source": "public_records",
    "year_built": 1994,
    "lot_acres": 0.24,
    "property_type": "Single-family",
    "seller_condition": "Dated",
    "seller_kitchen_condition": "Dated",
    "seller_bathroom_condition": "Fixer upper",
    "flags": "Roof age",
    "seller_minimum": 186000,
    "calculated_offer": 191400,
    "offer_shown": 188000,
    "smart_offer": true,
    "room_kept": 3400,
    "gate_result": "smart_offer",
    "offer_type": "cash",
    "estimated_sale_price": null,
    "cash_offer": null,
    "seller_choice": null,
    "seller_choice_at": null,
    "is_test": false,
    "seller_response": null,
    "counter_amount": null,
    "counter_note": null,
    "closing_preference": null,
    "responded_at": null,
    "tags": "",
    "seller_finance_mortgage": null,
    "condition_mismatch": false,
    "county_condition_tier": "Dated",
    "offer_at_county_condition": 191400,
    "deal_status": "new",
    "tcpa_consent_at": "2026-10-01T15:04:05.000Z",
    "lead_url": "https://app.briskoffers.com/app/offers?lead=00000000-0000-4000-8000-000000000000",
    "lead_created_at": "2026-10-01T15:00:00.000Z"
  }
]

Or find a lead:

curl "https://app.briskoffers.com/api/v1/leads?phone=5555550100" \
  -H "Authorization: Bearer brk_live_XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX"

Errors: 400 (phone too short), 401, 403, 429.

GET/leads/{id}#

One lead by its lead_id. An unknown id, or one that belongs to another account, returns 404.

curl "https://app.briskoffers.com/api/v1/leads/00000000-0000-4000-8000-000000000000" \
  -H "Authorization: Bearer brk_live_XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX"
Response · 200
JSON
{
  "lead_id": "00000000-0000-4000-8000-000000000000",
  "seller_first_name": "TEST",
  "seller_last_name": "Seller",
  "seller_phone": "(555) 555-0100",
  "seller_email": "test.seller@example.com",
  "address_line1": "123 Test St",
  "unit": null,
  "city": "Raleigh",
  "state": "NC",
  "zip": "27610",
  "county": "Wake",
  "beds": 3,
  "baths": 2,
  "sqft": 1840,
  "sqft_source": "public_records",
  "year_built": 1994,
  "lot_acres": 0.24,
  "property_type": "Single-family",
  "seller_condition": "Dated",
  "seller_kitchen_condition": "Dated",
  "seller_bathroom_condition": "Fixer upper",
  "flags": "Roof age",
  "seller_minimum": 186000,
  "calculated_offer": 191400,
  "offer_shown": 188000,
  "smart_offer": true,
  "room_kept": 3400,
  "gate_result": "smart_offer",
  "offer_type": "cash",
  "estimated_sale_price": null,
  "cash_offer": null,
  "seller_choice": null,
  "seller_choice_at": null,
  "is_test": false,
  "seller_response": null,
  "counter_amount": null,
  "counter_note": null,
  "closing_preference": null,
  "responded_at": null,
  "tags": "",
  "seller_finance_mortgage": null,
  "condition_mismatch": false,
  "county_condition_tier": "Dated",
  "offer_at_county_condition": 191400,
  "deal_status": "new",
  "tcpa_consent_at": "2026-10-01T15:04:05.000Z",
  "lead_url": "https://app.briskoffers.com/app/offers?lead=00000000-0000-4000-8000-000000000000",
  "lead_created_at": "2026-10-01T15:00:00.000Z"
}

Errors: 401, 403, 404, 429.

PATCH/leads/{id}/status#

Sets the deal status, the same way your team does in the portal. The change is recorded in the lead’s history, counts toward closed-deal tracking, and fires a lead.updated webhook with update_reason: "deal_status" when the status actually changes. Returns the updated lead.

NameTypeRequiredNotes
statusstringRequiredOne of new, offer_accepted, counter_submitted, contacted, appointment, listing_signed, listed, under_contract, closed and dead. Labels like “Under contract” are accepted and normalized. listing_signed and listed are for real estate agent accounts only (other accounts get a 400), and agents can also send “Listing appointment” for appointment.
contract_pricenumberOptionalDollars, positive, at most 100,000,000. Kept only on closed.
close_datestringOptionalYYYY-MM-DD. Kept only on closed.
dead_reasonstringOptionalOne of price, not_motivated, unreachable and other. Required on dead; ignored otherwise.

Mark a deal closed:

curl -X PATCH "https://app.briskoffers.com/api/v1/leads/00000000-0000-4000-8000-000000000000/status" \
  -H "Authorization: Bearer brk_live_XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX" \
  -H "Content-Type: application/json" \
  -d '{"status":"closed","contract_price":185000,"close_date":"2026-11-02"}'
Response · 200
JSON
{
  "lead_id": "00000000-0000-4000-8000-000000000000",
  "seller_first_name": "TEST",
  "seller_last_name": "Seller",
  "seller_phone": "(555) 555-0100",
  "seller_email": "test.seller@example.com",
  "address_line1": "123 Test St",
  "unit": null,
  "city": "Raleigh",
  "state": "NC",
  "zip": "27610",
  "county": "Wake",
  "beds": 3,
  "baths": 2,
  "sqft": 1840,
  "sqft_source": "public_records",
  "year_built": 1994,
  "lot_acres": 0.24,
  "property_type": "Single-family",
  "seller_condition": "Dated",
  "seller_kitchen_condition": "Dated",
  "seller_bathroom_condition": "Fixer upper",
  "flags": "Roof age",
  "seller_minimum": 186000,
  "calculated_offer": 191400,
  "offer_shown": 188000,
  "smart_offer": true,
  "room_kept": 3400,
  "gate_result": "smart_offer",
  "offer_type": "cash",
  "estimated_sale_price": null,
  "cash_offer": null,
  "seller_choice": null,
  "seller_choice_at": null,
  "is_test": false,
  "seller_response": null,
  "counter_amount": null,
  "counter_note": null,
  "closing_preference": null,
  "responded_at": null,
  "tags": "",
  "seller_finance_mortgage": null,
  "condition_mismatch": false,
  "county_condition_tier": "Dated",
  "offer_at_county_condition": 191400,
  "deal_status": "closed",
  "tcpa_consent_at": "2026-10-01T15:04:05.000Z",
  "lead_url": "https://app.briskoffers.com/app/offers?lead=00000000-0000-4000-8000-000000000000",
  "lead_created_at": "2026-10-01T15:00:00.000Z"
}

Mark a deal dead:

curl -X PATCH "https://app.briskoffers.com/api/v1/leads/00000000-0000-4000-8000-000000000000/status" \
  -H "Authorization: Bearer brk_live_XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX" \
  -H "Content-Type: application/json" \
  -d '{"status":"dead","dead_reason":"price"}'
Response · 200
JSON
{
  "lead_id": "00000000-0000-4000-8000-000000000000",
  "seller_first_name": "TEST",
  "seller_last_name": "Seller",
  "seller_phone": "(555) 555-0100",
  "seller_email": "test.seller@example.com",
  "address_line1": "123 Test St",
  "unit": null,
  "city": "Raleigh",
  "state": "NC",
  "zip": "27610",
  "county": "Wake",
  "beds": 3,
  "baths": 2,
  "sqft": 1840,
  "sqft_source": "public_records",
  "year_built": 1994,
  "lot_acres": 0.24,
  "property_type": "Single-family",
  "seller_condition": "Dated",
  "seller_kitchen_condition": "Dated",
  "seller_bathroom_condition": "Fixer upper",
  "flags": "Roof age",
  "seller_minimum": 186000,
  "calculated_offer": 191400,
  "offer_shown": 188000,
  "smart_offer": true,
  "room_kept": 3400,
  "gate_result": "smart_offer",
  "offer_type": "cash",
  "estimated_sale_price": null,
  "cash_offer": null,
  "seller_choice": null,
  "seller_choice_at": null,
  "is_test": false,
  "seller_response": null,
  "counter_amount": null,
  "counter_note": null,
  "closing_preference": null,
  "responded_at": null,
  "tags": "",
  "seller_finance_mortgage": null,
  "condition_mismatch": false,
  "county_condition_tier": "Dated",
  "offer_at_county_condition": 191400,
  "deal_status": "dead",
  "tcpa_consent_at": "2026-10-01T15:04:05.000Z",
  "lead_url": "https://app.briskoffers.com/app/offers?lead=00000000-0000-4000-8000-000000000000",
  "lead_created_at": "2026-10-01T15:00:00.000Z"
}

Errors: 400 (unknown status, missing dead_reason, bad price or date), 401, 403, 404, 429.

POST/hooks#

Subscribes a URL to an event (REST hooks; this is what the Zapier app uses). Returns 201 with the subscription’s id and its signing secret, which is shown once. An account can have up to 50 subscriptions created through the API.

NameTypeRequiredNotes
urlstringRequiredA public HTTPS URL. Private, loopback and cloud-metadata addresses are rejected.
eventstringRequiredlead.created and lead.updated.
campaign_idstring (uuid)OptionalOnly leads from this campaign. Must be one of your campaigns.
gate_resultstringOptionalOnly leads with this result (see which leads get sent). Applies to lead.created.
curl -X POST "https://app.briskoffers.com/api/v1/hooks" \
  -H "Authorization: Bearer brk_live_XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX" \
  -H "Content-Type: application/json" \
  -d '{"url":"https://example.com/brisk-leads","event":"lead.created","gate_result":"qualified"}'
Response · 201
JSON
{
  "id": "00000000-0000-4000-8000-0000000000c1",
  "event": "lead.created",
  "url": "https://example.com/brisk-leads",
  "signing_secret": "whsec_XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX"
}

Errors: 400 (bad URL, unknown event or campaign, too many subscriptions), 401, 403, 429.

DELETE/hooks/{id}#

Removes a subscription created through the API. Webhooks added in the portal can’t be removed this way (they return 404).

curl -X DELETE "https://app.briskoffers.com/api/v1/hooks/00000000-0000-4000-8000-0000000000c1" \
  -H "Authorization: Bearer brk_live_XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX"
Response · 200
JSON
{
  "id": "00000000-0000-4000-8000-0000000000c1",
  "deleted": true
}

Errors: 401, 403, 404, 429.

Webhooks#

Brisk posts a JSON event to your URL when a lead is created or updated. Set one up in the portal (Integrations → Webhooks: URL, events, campaigns, results) or with POST /hooks.

Events#

EventWhen
lead.createdA seller finished your link, whatever the result. Filter on gate_result if you only want some.
lead.updatedSomething changed on an existing lead. update_reason says what: deal_status The deal status changed (in the portal or through the API). · condition_verified Someone on your team verified the condition after a mismatch. · resent You clicked Resend on a delivery or resumed a paused webhook. · seller_choice On a real estate agent's campaign, the seller picked "List it with me" or the cash option (seller_choice and tags change). · seller_response The seller answered their offer: they said they are interested, or sent a counteroffer (seller_response, counter_amount and deal_status change).

The request#

A POST with a JSON body and these headers:

HeaderValue
Content-Typeapplication/json
Brisk-Signaturet=<unix seconds>,v1=<hex>: the HMAC-SHA256 of <t>.<raw body> with your signing secret.
Brisk-Event-IdThe event’s id. The same on every retry and resend.
User-AgentBrisk-Webhooks/1.0 (+https://briskoffers.com)

The body is an envelope around the lead: event_id, event_type, api_version, account_id, campaign_id, campaign_name, update_reason (on updates), test: true (on test sends), then every lead field, then created_at (the event’s own time; the lead’s is lead_created_at), then raw. The full sample:

lead.created
{
  "event_id": "00000000-0000-4000-8000-0000000000e1",
  "event_type": "lead.created",
  "api_version": "2026-10-01",
  "account_id": "00000000-0000-4000-8000-00000000000a",
  "campaign_id": "00000000-0000-4000-8000-00000000000b",
  "campaign_name": "Raleigh – Direct mail",
  "lead_id": "00000000-0000-4000-8000-000000000000",
  "seller_first_name": "TEST",
  "seller_last_name": "Seller",
  "seller_phone": "(555) 555-0100",
  "seller_email": "test.seller@example.com",
  "address_line1": "123 Test St",
  "unit": null,
  "city": "Raleigh",
  "state": "NC",
  "zip": "27610",
  "county": "Wake",
  "beds": 3,
  "baths": 2,
  "sqft": 1840,
  "sqft_source": "public_records",
  "year_built": 1994,
  "lot_acres": 0.24,
  "property_type": "Single-family",
  "seller_condition": "Dated",
  "seller_kitchen_condition": "Dated",
  "seller_bathroom_condition": "Fixer upper",
  "flags": "Roof age",
  "seller_minimum": 186000,
  "calculated_offer": 191400,
  "offer_shown": 188000,
  "smart_offer": true,
  "room_kept": 3400,
  "gate_result": "smart_offer",
  "offer_type": "cash",
  "estimated_sale_price": null,
  "cash_offer": null,
  "seller_choice": null,
  "seller_choice_at": null,
  "is_test": false,
  "seller_response": null,
  "counter_amount": null,
  "counter_note": null,
  "closing_preference": null,
  "responded_at": null,
  "tags": "",
  "seller_finance_mortgage": null,
  "condition_mismatch": false,
  "county_condition_tier": "Dated",
  "offer_at_county_condition": 191400,
  "deal_status": "new",
  "tcpa_consent_at": "2026-10-01T15:04:05.000Z",
  "lead_url": "https://app.briskoffers.com/app/offers?lead=00000000-0000-4000-8000-000000000000",
  "lead_created_at": "2026-10-01T15:00:00.000Z",
  "created_at": "2026-10-01T15:04:06.000Z",
  "raw": {
    "status": "smart",
    "step": "done",
    "formula_version": 1,
    "offer_preset": null,
    "tags": [],
    "address": {
      "line1": "123 Test St",
      "city": "Raleigh",
      "state": "NC",
      "zip": "27610",
      "formatted": "123 Test St, Raleigh, NC 27610"
    },
    "property": {
      "sqft": 1840,
      "beds": 3,
      "baths": 2,
      "yearBuilt": 1994,
      "lotAcres": 0.24,
      "propertyType": "Single-family",
      "county": "Wake"
    },
    "property_source": null,
    "seller_edited_fields": [],
    "condition_tier": 1,
    "answers": {
      "occupancy": "Owner",
      "kitchen": 1,
      "bath": 0
    },
    "flags": [
      "roof"
    ],
    "arv": 315000,
    "offer_low": null,
    "breakdown": null,
    "county_condition_score": 8,
    "county_condition_tier_index": 1,
    "county_assessment_year": null,
    "condition_verified_at": null,
    "deal": {
      "status": "new",
      "dead_reason": null,
      "contract_price": null,
      "close_date": null,
      "changed_at": null
    },
    "consent": {
      "given": true,
      "text": "By checking this box, I agree that … may contact me …",
      "at": "2026-10-01T15:04:05.000Z"
    },
    "completed_at": "2026-10-01T15:04:05.000Z"
  }
}

Verifying signatures#

Every delivery is signed with the endpoint’s secret (shown once when you create the webhook). Recompute the HMAC over the raw request body, before you parse the JSON, compare it in constant time, and reject timestamps more than 5 minutes old. Parsing and re-serializing the body first changes the bytes and the check fails.

Node.js
import crypto from "node:crypto";

// rawBody: the request body exactly as received (a string, before JSON.parse)
export function verifyBrisk(rawBody, header, secret) {
  const parts = Object.fromEntries(header.split(",").map((kv) => kv.split("=")));
  const t = Number(parts.t);
  if (Math.abs(Date.now() / 1000 - t) > 300) return false; // older than 5 minutes
  const expected = crypto.createHmac("sha256", secret).update(`${t}.${rawBody}`).digest("hex");
  return parts.v1?.length === expected.length &&
    crypto.timingSafeEqual(Buffer.from(parts.v1), Buffer.from(expected));
}

// verifyBrisk(rawBody, req.headers["brisk-signature"], process.env.BRISK_SIGNING_SECRET)
Python
import hashlib, hmac, time

def verify_brisk(raw_body: bytes, header: str, secret: str) -> bool:
    # raw_body: the request body exactly as received, before any JSON parsing
    parts = dict(kv.split("=", 1) for kv in header.split(","))
    t = int(parts.get("t", "0"))
    if abs(time.time() - t) > 300:  # older than 5 minutes
        return False
    expected = hmac.new(secret.encode(), f"{t}.".encode() + raw_body, hashlib.sha256).hexdigest()
    return hmac.compare_digest(parts.get("v1", ""), expected)

# verify_brisk(request.get_data(), request.headers["Brisk-Signature"], os.environ["BRISK_SIGNING_SECRET"])

Delivery and retries#

  • Any 2xx response counts as delivered. Brisk waits up to 10 seconds and doesn’t follow redirects.
  • After a failure Brisk retries after 1 minute, 5 minutes, 30 minutes, 2 hours, 6 hours and 12 hours: about 21 hours in total. Respond quickly and do your work afterwards.
  • After 3 consecutive failed events, the account owner gets an email.
  • After 3 days of continuous failure the webhook is paused and new events are held. Resume and resend missed leads on the Integrations page sends everything that was missed.

Idempotency#

Retries, the Resend button and resuming a paused webhook all reuse the same event_id, so de-duplicate on Brisk-Event-Id. Events can arrive out of order; use created_at to decide which is newest.

Testing#

Send test lead on the Integrations page posts a clearly labeled sample (the seller is “TEST Seller”) with test: true in the body. A test send gets one try and doesn’t count toward failure handling. For a first look, point a webhook at a request-inspector site such as webhook.site, then at your real endpoint.

test send (excerpt)
{
  "event_id": "00000000-0000-4000-8000-0000000000e1",
  "event_type": "lead.created",
  "api_version": "2026-10-01",
  "test": true,
  "seller_first_name": "TEST",
  "seller_last_name": "Seller",
  "…": "…"
}

Lead field reference#

Every field on a lead, in webhooks and in API responses. Money is in whole dollars, times are ISO 8601 in UTC.

FieldTypeNullableDescriptionExample
Seller
lead_idstring (uuid)NoBrisk's id for the lead. Use it with GET /leads/{id} and PATCH /leads/{id}/status.00000000-0000-4000-8000-000000000000
seller_first_namestringNoThe seller's first name(s), split from the name they typed. One word goes here when there's no last name.TEST
seller_last_namestringNoThe seller's last name (the last word of what they typed). Empty when they gave one word.Seller
seller_phonestring | nullYesThe phone number as the seller typed it. Null only on leads that never reached the contact step.(555) 555-0100
seller_emailstring | nullYesThe email as the seller typed it.test.seller@example.com
Address
address_line1string | nullYesStreet address of the property.123 Test St
unitstring | nullYesUnit or apartment number, when the address had one.—
citystring | nullYesCity of the property.Raleigh
statestring | nullYesTwo-letter state code.NC
zipstring | nullYesFive-digit ZIP code.27610
countystring | nullYesCounty, from public records. Null when the lookup found none.Wake
Property
bedsnumber | nullYesBedrooms, from public records or the seller's correction.3
bathsnumber | nullYesBathrooms. Halves are possible (2.5).2
sqftnumber | nullYesLiving area in square feet, from public records or entered by the seller (see sqft_source).1840
sqft_sourcestring | nullYesWho the square footage came from: seller (they entered it because public records had none, or they changed it) or public_records. Null when there is no size. A seller-entered size is not verified.public_records
year_builtnumber | nullYesYear the home was built.1994
lot_acresnumber | nullYesLot size in acres.0.24
property_typestring | nullYesProperty type as the seller confirmed it, e.g. Single-family, Townhome, Condo, Multi-family, Mobile or manufactured.Single-family
Condition
seller_conditionstring | nullYesThe seller's overall condition answer, as a label (see the enumerations below). Sets the percent of ARV in your formula.Dated
seller_kitchen_conditionstring | nullYesThe seller's kitchen condition answer, same labels. Null on leads from before October 22, 2026.Dated
seller_bathroom_conditionstring | nullYesThe seller's bathroom condition answer, same labels.Fixer upper
flagsstringNoIssues the seller reported, as labels separated by commas (see the enumerations below). Empty when none. The list form is raw.flags.Roof age
condition_mismatchbooleanNoTrue when the county's condition record is a lot better than what the seller said, so the offer may deserve a second look before you call.false
county_condition_tierstring | nullYesThe condition the county record implies, as a label. Null when the county has no usable record.Dated
offer_at_county_conditionnumber | nullYesWhat your formula would produce at the county's condition instead of the seller's, in dollars. Null when there's no county record.191400
Offer
offer_typestringNoWhat the seller was priced for: cash (a cash offer, on investor and agency campaigns), listing (a real estate agent's estimated sale price) or listing_and_cash (the estimate with a cash option beside it).cash
seller_minimumnumber | nullYesThe lowest price the seller said they'd accept, in dollars. Null when they didn't reach that question.186000
calculated_offernumber | nullYesWhat your formula produced for this home, in dollars, whether or not the seller saw it. Null when no number could be calculated (needs lookup, didn't finish).191400
offer_shownnumber | nullYesThe number the seller actually saw: the Smart Offer on a Smart Offer lead, the calculated offer on a Qualified lead. Null when no number was shown (Cold, Manual review, Seller Financing, Disqualified, didn't finish).188000
smart_offerbooleanNoTrue when Smart Offer showed the seller a number just above their own minimum instead of the full calculated offer.true
room_keptnumber | nullYesOn a Smart Offer lead: calculated_offer minus offer_shown, the margin you kept. Null otherwise.3400
estimated_sale_pricenumber | nullYesOn an agent campaign, the estimated sale price the seller saw, in dollars. Null on cash-offer campaigns and when no number was shown.null
cash_offernumber | nullYesOn an agent campaign with the cash option on, the cash number shown beside the estimate, in dollars. Null when no cash card was shown.null
Seller response
seller_responsestring | nullYesThe seller's answer to the offer they saw: accepted ("I'm interested at this price") or countered. Null until they answer, and on leads that were shown no number. Neither is a contract. An answer fires lead.updated with update_reason seller_response, and moves a New lead to the Offer accepted or Counter submitted deal status.null
counter_amountnumber | nullYesOn a countered lead, the price the seller proposed, in dollars. It is always above offer_shown. Null otherwise.null
counter_notestring | nullYesOn a countered lead, what the seller added under "Anything we should know?" (up to 500 characters). Null when they added nothing.null
closing_preferencestring | nullYesWhen the seller said they'd like to close: asap, 30_days, 30_60_days or flexible. Null when they didn't say or haven't answered.null
responded_atstring (ISO 8601) | nullYesWhen the seller answered. Null until then.—
Result and status
gate_resultstringNoWhat happened at the price question: the lead's result (see the enumerations below). Filter on it to send only the leads you want to your CRM.smart_offer
seller_choicestring | nullYesOn an agent campaign, which card the seller picked: list ("List it with me") or cash ("Cash option"). Null until they pick, and on every other campaign. A pick fires lead.updated with update_reason seller_choice.null
seller_choice_atstring (ISO 8601) | nullYesWhen the seller picked. Null until then.—
tagsstringNoCRM tags, separated by commas: "Seller Financing" on Seller Financing leads; "Wants to list", "Wants cash" and "High asking price" on agent-campaign leads. Empty when none. The list form is raw.tags.—
seller_finance_mortgagestring | nullYesOn a Seller Financing lead, the seller's answer to "Do you have a mortgage?": free_clear, mortgage or not_sure. Null on every other lead.—
deal_statusstringNoWhere the deal stands in your pipeline (see the enumerations below). Starts at new; you or the API move it. listing_signed and listed are for real estate agent accounts only.new
is_testbooleanNoTrue for a test lead a real estate agent made with "See what sellers see". Test leads reach webhooks, Zapier and the API only when the account turns on "Send test leads to my CRM".false
Compliance
tcpa_consent_atstring (ISO 8601) | nullYesWhen the seller checked the consent box on the form. Null when they didn't consent to calls and texts.2026-10-01T15:04:05.000Z
Links and timestamps
lead_urlstring (URL)NoA link that opens this lead in the Brisk portal. Good for a note or link field in your CRM.https://app.briskoffers.com/app/offers?lead=00000000-0000-4000-8000-000000000000
lead_created_atstring (ISO 8601) | nullYesWhen the seller submitted the form. On a webhook, the envelope's created_at is the event's own time, which is later for updates.2026-10-01T15:00:00.000Z

Enumerations#

gate_result

ValueLabel
qualifiedQualified
smart_offerSmart offer
seller_financeSeller Financing
high_asking_priceHigh asking price
coldCold
manual_reviewManual review
disqualifiedDisqualified
pending_lookupNeeds lookup (account was out of lookups)
in_progressDidn't finish

deal_status

ValueLabel
newNew
offer_acceptedOffer accepted
counter_submittedCounter submitted
contactedContacted
appointmentAppointment (Listing appointment for agent accounts)
listing_signedListing signed (agent accounts only)
listedListed (agent accounts only)
under_contractUnder contract
closedClosed
deadDead

dead_reason

ValueLabel
pricePrice
not_motivatedNot motivated
unreachableUnreachable
otherOther

seller_condition and the room conditions

LabelWhat the seller picked
Fixer upperFixer-upper
DatedOriginal & dated
StandardWell maintained
High endFully renovated
LuxuryHigh-end custom

flags

LabelShort
Roof ageOld roof
HVAC ageOld HVAC
Solar panelsSolar panels
Fire damageFire damage
AsbestosAsbestos
Mobile or manufacturedMobile / manufactured
Well waterWell water
Septic systemSeptic

seller_finance_mortgage

ValueMeaning
free_clearFree and clear
mortgageHas mortgage
not_sureNot sure

Fields people misread#

  • calculated_offer is what your formula produced; offer_shown is what the seller actually saw. They match on a Qualified lead. On a Smart Offer lead offer_shown is lower, and on Cold, Manual review, Seller Financing and Disqualified leads it is null because no number was shown.
  • smart_offer is true when Smart Offer showed a number just above the seller’s own minimum; room_kept is the difference you kept (calculated_offer minus offer_shown).
  • condition_mismatch flags a lead where the county’s record says the home is in better shape than the seller said. county_condition_tier is the county’s view and offer_at_county_condition is what your formula would produce at that condition, so you can compare before you call.
  • tags is a comma-separated string, ready for a tags field. The list form is raw.tags.
  • tcpa_consent_at is null when the seller didn’t check the consent box. Respect it before you call or text.

The raw object#

Webhook bodies also carry raw: the full detail behind the flat fields. It is for advanced use and may gain fields. It never contains session tokens, IP hashes or IP addresses.

KeyWhat it holds
statusThe lead's status as stored (smart, qualified, seller_finance, high_ask, cold, review, disqualified, pending_lookup, in_progress).
stepThe last step the seller completed on the form.
formula_versionThe version of your campaign's formula that priced this lead.
offer_presetThe offer strategy that version was published with: conservative, balanced, aggressive or custom. Null on leads from before presets.
tagsThe CRM tags as a list (the flat tags field joins them with commas).
seller_financeOnly on Seller Financing leads: { mortgage } with the seller's mortgage answer.
cashOnly on agent-campaign leads: { offer, formula_version } for the cash option, or null when no cash card was shown. The version names the Brisk investor formula that priced it.
seller_choiceOnly on agent-campaign leads: { choice, at } once the seller picked a card, or null.
addressThe address as structured fields plus the formatted string.
propertyProperty details from public records and the seller's corrections.
property_sourceWhere the property details came from.
seller_edited_fieldsWhich property fields the seller corrected on the form.
condition_tierThe seller's overall condition as an index (0 = Fixer upper … 4 = Luxury).
answersEvery answer the seller gave on the form, keyed by question.
flagsReported issues as a list of keys (roof, hvac, solar, fire, asbestos, mobile, well, septic).
arvThe after-repair value the formula used, in dollars.
offer_lowThe low end of the range, on campaigns that show a range.
breakdownThe line-by-line calculation behind calculated_offer.
county_condition_scoreThe county's condition score, when the county publishes one.
county_condition_tier_indexThe condition tier the county record implies, as an index.
county_assessment_yearThe year of the county record.
condition_verified_atWhen someone on your team verified the condition after a mismatch.
dealDeal status detail: status, dead_reason, contract_price, close_date, changed_at.
consentThe consent record: given, the exact text shown, and when.
completed_atWhen the seller reached the end of the form.

Versioning and changes#

Every webhook body carries api_version, currently 2026-10-01. Additive changes ship without a version change: new fields, new values in an enumeration, new update_reasons. Write your receiver to ignore fields it doesn’t know and to handle values it hasn’t seen. Breaking changes get a new api_version and notice by email before they take effect.

Changelog#

  • 2026-10-01Initial release: lead.created and lead.updated webhooks, the REST API (/me, /campaigns, /leads, /leads/{id}, /leads/{id}/status and /hooks) and the Zapier app.

Help#

Questions? Use Get help in the Brisk app or email hello@briskoffers.com. Include the Brisk-Event-Id or lead_id when you ask about a specific delivery.

Looking for Brisk itself? Instant offers for real estate investors. Methods used on this page: GETPOSTPATCHDELETE