Help

Connect your CRM

New leads land in the system you already run your jobs in, and what happens to each job — booked, sold — gets recorded back in WonFlo. One question decides how you set it up.

Last updated October 4, 2026

WonFlo is not a CRM and is not trying to become one. If you close jobs in JobNimbus, AccuLynx, Roofr, Jobber or anything else, the lead should show up there on its own — no export, no copy-paste, no second place to check.

Connecting goes both ways. Leads go into your CRM the moment they come in. Results come back — when a job is booked or sold, that is recorded on the lead in WonFlo, so you are not tapping the same thing in two places.

Everything lives in one place: in app.wonflo.com, go to Settings → Connect your CRM. It asks one question.

Which CRM do you use?

Pick your answer and jump to it. You only need to read your own section.

JobNimbus

WonFlo connects to JobNimbus directly. No Zapier, no webhook, no developer — just a key.

  1. Make a key in JobNimbus. In JobNimbus, go to Settings → API and create a new API key. Copy it. It is long — that is normal.
  2. Paste it into WonFlo. In WonFlo, go to Settings → Connect your CRM, choose JobNimbus, paste the key and save.
  3. That is it. Run one test lead through your funnel if you want to watch it land.

From then on:

Your key stays private
WonFlo stores the key locked up and never shows it back to you — not in Settings, not anywhere. If you ever need to change it, make a new one in JobNimbus and paste it in the same place.

Another CRM

For AccuLynx, Roofr, Jobber, QuoteIQ, ServiceTitan, HubSpot, GoHighLevel or anything else, the connection runs through Zapier, which talks to almost every CRM out there. There are two steps, and each one is a single Zap.

Step 1 works on its own if that is all you want. Step 2 needs Step 1 in place first, because it uses the WonFlo lead ID that Step 1 drops into your CRM.

Step 1 — Send new leads to your CRM

  1. Start a Zap. In Zapier, make a new Zap. For the trigger, pick Webhooks by Zapier and the event Catch Hook. Zapier hands you a web address — copy it.
  2. Paste it into WonFlo. In WonFlo, go to Settings → Connect your CRM → Another CRM → Step 1. Paste the address and save.
  3. Copy the signing secret. A box pops up with it. It is shown once. Zapier does not need it, but keep it with your passwords in case a developer ever does.
  4. Send a test lead. Run one test lead through your own funnel, so Zapier has a real lead to look at.
  5. Point it at your CRM. Add an action for your CRM — usually "create lead" or "create contact" — and match up the fields: name, phone, address and so on.
  6. Save the WonFlo lead ID in your CRM. Put the field Zapier shows as Lead Id into a custom field or the notes on the new record. You will need it for Step 2. Skip this and Step 2 has nothing to match on.
  7. Turn the Zap on.
Lost the secret? Saving again makes a new one
If you closed the box without copying the signing secret, save the address again. That makes a new secret and retires the old one. Harmless if nothing was using it yet — so re-save on purpose, not by accident.

How to tell it is working. New leads appear in your CRM, usually within about a minute. The CRM card on your WonFlo dashboard shows the last delivery, and it is the first place to look if someone says leads stopped arriving. If that card looks healthy and your CRM is empty, the problem is on the Zapier side — check the Zap is switched on, and look at its history.

Step 2 — Report job results back

Step 2 is a second Zap. When a job changes status in your CRM, it tells WonFlo what happened — booked, sold, lost and so on — and the result is recorded on that lead in WonFlo. Same result as tapping the button in your dashboard, without the tapping.

Before you start: Step 1 is working, and your CRM has the WonFlo lead ID saved on each new lead (step 6 above). If your CRM is JobNimbus, you do not need any of this — the JobNimbus section does it with one key.

  1. Make a WonFlo key. In WonFlo, go to Settings → Connect your CRM → Another CRM → Step 2 and tap Create key. WonFlo shows you two things, once: a key that starts with wfo_in_, and an address — https://api.wonflo.com/api/leads/{lead_id}/outcome. Copy both somewhere safe before you close the box. Treat the key like a password — anyone who has it can mark your leads.
  2. Start a new Zap with your CRM as the trigger. Pick your CRM's app in Zapier and the event that fires when a job changes status. Each CRM names it a little differently — look for something like "job status changed", "job updated" or "deal stage changed".
  3. Optional: add a Filter. Add Filter by Zapier so the Zap only carries on when the status is one you care about — for example, only when the status is your "Appointment Scheduled" status. The easiest setup is one Zap per result: build it once for booked, then copy the Zap and change the filter and the outcome for sold, lost and the rest.
  4. Add the action that talks to WonFlo. Pick Webhooks by Zapier, event POST, and fill in four boxes:
    Zapier box
    What goes in it
    URL
    The address from step 1, with {lead_id} — curly brackets and all — replaced by the WonFlo lead ID field from your CRM trigger. (Leads that came in through the JobNimbus connection carry it in the job description, after wonflo_lead_id=.)
    Payload Type
    json
    Data
    A row named outcome with the result, like booked. For a sale, a second row named value with the job's dollar amount. The examples below show every result.
    Headers
    A row named Authorization with the value Bearer, one space, then your key.
  5. Test it — see below — and then turn the Zap on.

What to send for each result

Five results, one word each. Only sold takes a dollar amount — a plain number like 12400 or $12,400 both work. Leave the amount out and WonFlo records your default job value instead. On any other result an amount is simply ignored.

When the job…
Send this
You reached them, nothing booked yet
{"outcome":"contacted"}
The inspection or appointment is on the calendar
{"outcome":"booked"}
The contract is signed
{"outcome":"sold","value":12400}
They went with someone else, or said no
{"outcome":"lost"}
It was never a real lead — wrong number, not the homeowner, spam
{"outcome":"junk"}

The $12,400 is only there to show the shape — send the real number from the job. Put together, a sale looks like this to WonFlo:

One sold job, start to finish
POST https://api.wonflo.com/api/leads/<WonFlo lead ID>/outcome
Authorization: Bearer <your wfo_in_ key>
Content-Type: application/json

{"outcome":"sold","value":12400}

Test it

  1. Use a test lead. Pick the test lead you ran through your funnel in Step 1 — it is in your CRM and in WonFlo.
  2. Send booked for it. Either move that lead's job to your booked status in your CRM, or use Test step on the webhook action in Zapier with that lead as the sample.
  3. Watch the lead in WonFlo. Open it in your dashboard. It should now show as booked.
  4. Check Settings. Under Connect your CRM → Another CRM → Step 2 you will see Last update received N min ago. That line is how you know the connection is working.

If the test lead did not change, Zapier's history shows the reply WonFlo sent back. Here is what each one means.

If something goes wrong

Zapier shows
What it means, and the fix
404 not_found
Wrong key or wrong lead ID. Check the header says Bearer, one space, then the whole key. Check the address has the WonFlo lead ID in it — not your CRM's own job number — and that {lead_id} is gone.
400
The result or the amount is not one WonFlo understands. Check the spelling — contacted, booked, sold, lost or junk — and that the amount is a dollar figure, or leave it out.
429
More than 120 updates in one minute. That usually means a bulk change in your CRM. Wait a minute and replay the failed runs in Zapier.
503
Reporting results is not switched on for your account yet. Email support and we will sort it out.
Replacing or revoking the key
Replace key makes a new key and the old one stops working — your Zap fails until you paste the new key into its Authorization header. Revoke stops the key immediately, with no replacement. Use Revoke if the key ever ends up somewhere it should not be.

I don't use a CRM

Then there is nothing to set up. Choose I don't use a CRM in Settings and you are done.

WonFlo is where you keep track. Every lead in your dashboard has Booked and Sold buttons — tap Booked once the inspection is on the calendar, and Sold with the job value once the contract is signed. It takes seconds, and it is recorded in WonFlo against that lead. How the closed loop works explains why that tap is worth making.

Your CRM is for filing, not alerting
Getting the lead into your CRM is where it goes to live. It is not how you find out it arrived. Lead alerts hit your phone and inbox in the first sixty seconds, and that is the one that decides whether you win the job.

For your developer

Everything above is all a roofer needs. This part is for whoever builds a receiver by hand instead of using Zapier, or wants to know exactly what goes over the wire.

What WonFlo sends in Step 1

A signed POST with a JSON body, on two events: lead.captured and lead.released. Branch on the event field — if all you want is new leads arriving in your CRM, act on lead.captured and ignore the rest.

Every payload carries schema_version "1.0", so a receiver can check what it is looking at before it starts reading fields. The top level is small:

Top-level key
What is in it
event
Which of the two events this is
delivered_at
When WonFlo sent it
lead
The lead itself — see below
contractor
The WonFlo account it belongs to

Inside lead, lead.id is the WonFlo lead ID — the value Step 2 puts in its address — and the dossier is grouped roughly the way the dashboard shows it: contact, property, estimate, tailored_answers, storm, attribution and device.

We are deliberately not printing a field-by-field dictionary here, because the honest way to see the payload is to look at a real one. Save the webhook, then run one test lead through your own funnel — Zapier's Catch Hook will show you every field it actually received, with real values in it, which beats any table we could write.

One field is worth knowing ahead of time: lead.estimate.is_area is true when the estimate range is an area estimate, because WonFlo had no roof measurement for that address, and false when the roof was measured or there is no range.

Verifying the signature

Zapier's catch hooks do not verify the signature by default, and for most roofing shops that is a perfectly reasonable place to land. The URL is long and unguessable, and the worst case is a junk row in your CRM. If you are posting to your own endpoint rather than to Zapier, verify before you trust. Every request carries this header:

Request header
X-WonFlo-Signature: t=<unix>,v1=<hex>

t is the Unix timestamp of the send. v1 is an HMAC-SHA256, in hex, computed over the string "<t>.<raw request body>" with your signing secret as the key.

Two rules decide whether this works at all:

Node — no dependencies
const crypto = require('crypto');

function verify(rawBody, header, secret) {
  // header looks like: t=1757500000,v1=9f8e7d...
  const p = Object.fromEntries(header.split(',').map(s => s.split('=')));

  const age = Math.abs(Date.now() / 1000 - Number(p.t));
  if (!(age < 300)) return false;               // 5-minute replay window

  const expected = crypto
    .createHmac('sha256', secret)
    .update(p.t + '.' + rawBody)                 // rawBody = the exact bytes
    .digest('hex');

  const a = Buffer.from(expected);
  const b = Buffer.from(p.v1 || '');
  return a.length === b.length && crypto.timingSafeEqual(a, b);
}

That is the whole recipe, and it is the same recipe in any language: split the header, check the age, HMAC-SHA256 the timestamp and the raw body together, compare in constant time. PHP, Python, Ruby, Go and C# all ship this in the standard library.

Delivery and retries

Sends ride a durable queue rather than going out inline with the homeowner's submit, so a slow or briefly unreachable receiver never affects the person filling out your funnel. In normal conditions a delivery lands within about a minute, and failures are retried.

The Step 2 endpoint

Any system that can make an HTTP request can report results — Zapier is just the easy way.

Related

Still stuck?

A real person reads every one of these, and it is usually faster than you would expect. Tell us your business name and what you were trying to do.

Email [email protected] ← Back to the help center