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. 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. 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. 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. 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

It follows Housecall Pro’s published API, but no customer account has run on it yet. If anything looks wrong, the delivery log on Connections says what happened to each booking.

Jobber through Zapier

  1. 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 is data customer_first_name in Zapier’s field picker.

  2. 2

    Find or create the client

    Add Jobber’s Find or Create Client. Map the name from customer_first_name and customer_last_name, and the phone from customer_phone_e164.

  3. 3

    Create the request

    Add Jobber’s Create Request for that client. Put service in the title and booking_id in it too, so the result can find its way back; add call_summary, service_address and scheduled_at_local to 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. 1

    Catch our booking

    Same as for Jobber: a Catch Hook trigger and a Webhook integration with the Booking made event.

  2. 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_id as the external id, the caller’s name for the name, and service plus call_summary for the summary.

  3. 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 applied means the booking changed; recorded means we noted it and nothing needed changing.
  • ignored means 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.
  • duplicate means 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 unit set to dollars. 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 unit set to cents: 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 unit set to dollars. 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

The Code step needs our 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.