Send Bookings to Your Field-Service Software
Housecall Pro, Jobber and ServiceTitan: bookings in, finished jobs back.
On this page
CaughtLeads can put each booking into the software your office already runs on, and take the finished job back out of it. That second half means a held appointment’s revenue is the job’s total from your software, not a typed estimate.
What each tool gets
- Housecall Pro (beta)
- A direct connection. Each booking becomes a lead in Job Inbox, on the caller's customer record. Completed and paid jobs come back by webhook with their totals.
- Jobber
- Through your own Zapier today: a client and a request.
- ServiceTitan
- Through your own Zapier: a booking on the Call Booking screen, which a CSR accepts or dismisses. ServiceTitan only lets certified partners connect directly, so we do not.
- Anything else
- Our signed webhook reaches anything Zapier, Make or n8n can talk to, and the job results URL takes the result back.
Housecall Pro (beta)
Housecall Pro offers its API on the MAX plan only. Nothing reaches your dispatch board by itself: bookings arrive as leads in Job Inbox, and your office converts them as usual.
- 1
Generate an API key
In Housecall Pro, an admin opens My Apps, then API Key Management, and generates a key with full access.
- 2
Connect it
In Connections, under Integrations, choose Add integration, then Housecall Pro, and paste the key. We check it with Housecall Pro before saving it, encrypted. It is never shown again.
- 3
Watch the first booking land
We look the caller up by phone. If exactly one customer has that number, the lead goes on that customer; otherwise we create one. The lead’s note carries the service, the requested time, the address, a short call summary and a line reading CaughtLeads booking followed by our booking id. In Connections, the delivery shows as synced with the lead number.
- 4
Turn on finished jobs
Open the key icon on the Housecall Pro connection and copy the webhook URL. Add it as a webhook in Housecall Pro’s settings, then paste the signing secret Housecall Pro shows you back into that dialog. From then on, a job converted from one of our leads that is completed or paid marks the booking held with the job total.
Why beta
Jobber through Zapier
- 1
Catch our booking
In Zapier, start a Zap with Webhooks by Zapier, Catch Hook. In CaughtLeads, add a Webhook integration with that URL and the Booking made event, then book a test call so Zapier has a real booking to sample (the send-a-test button sends no booking fields). In the Catch Hook every field arrives under
data, so the caller’s first name isdata customer_first_namein Zapier’s field picker. - 2
Find or create the client
Add Jobber’s Find or Create Client. Map the name from
customer_first_nameandcustomer_last_name, and the phone fromcustomer_phone_e164. - 3
Create the request
Add Jobber’s Create Request for that client. Put
servicein the title andbooking_idin it too, so the result can find its way back; addcall_summary,service_addressandscheduled_at_localto the details. Zapier’s Jobber actions use the client’s first property when there are several.
ServiceTitan through Zapier
ServiceTitan’s Zapier app is offered on The Works and Legacy packages, and ServiceTitan asks you to register for it. We never ask for your ServiceTitan keys: their API terms do not allow it.
- 1
Catch our booking
Same as for Jobber: a Catch Hook trigger and a Webhook integration with the Booking made event.
- 2
Create the booking
Add ServiceTitan’s Create Booking. It needs a booking provider (pick yours), a source, a name, a summary, whether this is a first-time client, and an external id. Use
booking_idas the external id, the caller’s name for the name, andservicepluscall_summaryfor the summary. - 3
A CSR accepts it
The booking waits on the Call Booking screen and in the Unassigned column of the dispatch board’s job tray until someone accepts or dismisses it.
Sending finished jobs back
Any tool can tell CaughtLeads a job was completed or cancelled. A completed job with a total marks the booking held with that amount, so send the final invoice total, never a quote or estimate: whatever total you send is what we count. A total of $0 is not counted. A later correction updates the amount and keeps the date it was first held. A cancellation cancels the booking, unless it is already held. If you mark a booking yourself, your mark stands: later updates from your software no longer change it.
The job results URL and secret
Every webhook integration has its own job results URL and secret. They are shown once, when you create the integration, or when you press the link icon on it (which replaces the secret). Keep the secret in your Zap; we cannot show it again.
A Zap that sends a result
Each result is a POST to your job results URL, with a JSON body and three headers that prove it came from you, the same scheme we sign our own requests with. A Code by Zapier (JavaScript) step can do it in one go; give it the inputs url, secret, booking_id, status, total, unit, job_id and occurred_at. unit is required: cents for Housecall Pro, dollars for Jobber and ServiceTitan. A failed send fails the step, so Zapier shows it and you can replay the run:
const crypto = require("crypto");
// Fill these in from the Zap's earlier steps. unit is REQUIRED:
// "cents" for Housecall Pro (its amounts are in cents: 124000 means $1,240.00)
// "dollars" for Jobber and ServiceTitan
const unit = String(inputData.unit || "").trim().toLowerCase();
if (unit !== "cents" && unit !== "dollars") {
throw new Error('Set the unit input to "cents" (Housecall Pro) or "dollars" (Jobber, ServiceTitan).');
}
const raw = inputData.total ? Number(inputData.total) : null;
if (raw !== null && !Number.isFinite(raw)) throw new Error("total is not a number: " + inputData.total);
const totalUsd = raw === null ? null : unit === "cents" ? raw / 100 : raw;
const body = JSON.stringify({
booking_id: inputData.booking_id, // our booking id
status: inputData.status, // "completed" or "canceled"
invoice_total_usd: totalUsd, // the FINAL invoice total, in dollars
external_job_id: inputData.job_id || null, // your tool's job number
occurred_at: inputData.occurred_at || new Date().toISOString(), // when it happened in your tool
});
// Same booking, job, status and total = same id, so a Zapier retry is not counted twice.
const id = ["zap", inputData.booking_id, inputData.job_id, inputData.status, inputData.total].join("_");
const ts = Math.floor(Date.now() / 1000).toString();
const key = Buffer.from(inputData.secret.replace(/^whsec_/, ""), "base64");
const sig = crypto.createHmac("sha256", key).update(id + "." + ts + "." + body).digest("base64");
const res = await fetch(inputData.url, {
method: "POST",
headers: {
"content-type": "application/json",
"webhook-id": id,
"webhook-timestamp": ts,
"webhook-signature": "v1," + sig,
},
body,
});
const answer = await res.text();
// A failed send must fail the step, so Zapier shows it and the run can be replayed.
if (!res.ok) throw new Error("CaughtLeads answered " + res.status + ": " + answer.slice(0, 200));
return { status: res.status, answer };- An answer of
appliedmeans the booking changed;recordedmeans we noted it and nothing needed changing. ignoredmeans we deliberately did not apply it, with the reason: an older update than one already applied, a cancellation of a held booking, a booking you marked yourself, or a connection that was removed or switched off.duplicatemeans we already had that exact message.- A 401 means the signature did not match, or the timestamp was more than five minutes off.
- A 404 means that booking id is not one of your bookings.
- A 422 means the body is not right: a booking id that is not a uuid, an unknown status, a negative total, or a time in the future.
Recipes by tool
- Jobber
- Trigger on Jobber’s Invoice Updated, add a Filter step that continues only when the invoice is sent or paid (never a draft), then the Code step with status completed, the invoice total and
unitset todollars. Job Closed carries the job’s value, not an invoice: never send that as the total. - Housecall Pro
- The direct connection above is the better route. Through Zapier, trigger on New Completed Job, then the Code step with
unitset tocents: Housecall Pro’s amounts are in cents, and the step divides by 100 so we receive dollars. - ServiceTitan
- Trigger on a completed job, add a Filter step that continues only when the status is Completed, and send the final invoice total with
unitset todollars. Never trigger on a new job or an unposted invoice. ServiceTitan’s triggers check on a schedule, so results arrive a little after the fact.
The result has to name our booking
booking_id. We put it on everything we create (the Housecall Pro lead note, the Jobber request title if you mapped it, the ServiceTitan booking’s external id). If the job your office made from it no longer carries it, use a Formatter by Zapier step to pull it out of the text, or leave that job out. We never guess which booking a result belongs to.What a booking carries
Besides the service and the time, a new booking carries what a field-service tool needs to book the job, from what the call captured: the caller’s name, phone (as customer_phone_e164 and the ten-digit customer_phone10), email, service address (split into street, city, state and ZIP only when it was said in exactly that shape), the time in your own timezone with the zone’s name, a short call summary, whether the caller raised a safety emergency, and a link to the call in your dashboard. It never carries the transcript.
The detail column says why, in plain words: a key that stopped working, a caller number Housecall Pro cannot store, or a booking queued too long ago to be useful as new work. Add that booking by hand if you still need it.
We could not tie it to one of your bookings, so it changed nothing. It stays listed under Job results so you can see it. Mark the booking held yourself if it was ours.