For developers and do-it-yourselfers
Connect Guided Path to Zapier, Make, n8n or your own code
Send new jobs to a Google Sheet, post in Slack when an invoice is paid, add website form requests as jobs, put new customers on your email list. Guided Path has a simple API and webhooks for that, free on every plan.
- Webhooks tell another app the moment something happens (a job is marked complete, an invoice is paid).
- The API lets another app read your customers, jobs, estimates and invoices, and add customers and unscheduled jobs.
Both are set up by an owner or admin in the Guided Path app under More → Connect other apps. Questions? Email help@guidedpathhomeservices.com.
Zapier
Use the built-in “Webhooks by Zapier” app. It needs a paid Zapier plan (Zapier counts it as a premium app). There’s no separate Guided Path app to install.
When something happens in Guided Path (a trigger)
- In Zapier, make a new Zap. For the trigger pick Webhooks by Zapier, then Catch Hook. Copy the webhook address Zapier shows you.
- In Guided Path, go to More → Connect other apps → Webhooks → Add a webhook. Paste the address, tick what to send (for example “A job is marked complete”) and save.
- Tap Send a test. Back in Zapier, tap Test trigger: the test shows up, with a sample of a real event inside it.
- For real field names, make the thing happen once in the app (mark a test job complete), then test the trigger again. Ticked more than one event? Add a Filter step on
event. - Add your action (Google Sheets, Slack, Gmail…) and use the fields under
data. Turn the Zap on.
Rather poll than receive? Pick Webhooks by Zapier → Retrieve Poll instead, with the URL https://app.guidedpathhomeservices.com/api/v1/events?event=job.completed, a header Authorization set to Bearer gp_live_YOUR_KEY, Key data and Deduplication Key id. Zapier checks every few minutes on most plans.
Do something in Guided Path (an action)
- In Guided Path, make an API key (More → Connect other apps → Make an API key) and tick “Also let it add customers and jobs”. Copy it: it’s shown once.
- In your Zap, add an action: Webhooks by Zapier → POST.
- URL:
https://app.guidedpathhomeservices.com/api/v1/customers(or/jobs). Payload Type:json. Under Data, add the fields, likename,email,phone. - Under Headers, add
Authorizationwith the valueBearer gp_live_YOUR_KEY. Test the step: the new customer is in your app.
Make
Trigger: Custom webhook
- In a new scenario, add Webhooks → Custom webhook, tap Add, name it and copy the address.
- In Guided Path, add a webhook with that address (More → Connect other apps → Webhooks) and pick the events.
- Tap Send a test (or make the event happen), so Make learns the data structure. Use Redetermine data structure if you change events later.
Action: HTTP → Make a request
- URL
https://app.guidedpathhomeservices.com/api/v1/jobs, Method POST. - Headers:
Authorization=Bearer gp_live_YOUR_KEY. - Body type Raw, Content type JSON (application/json), and the JSON in Request content. Turn on Parse response to use the reply.
n8n and your own code
In n8n, a Webhook node (method POST, use its Production URL) receives events, and an HTTP Request node with a header Authorization: Bearer gp_live_YOUR_KEY calls the API. From your own code, any HTTP client works. Check the signature on webhooks you receive (below).
Your API key
An owner or admin makes keys in the app under More → Connect other apps. A key is shown once: keep it like a password. Each key belongs to one business and only ever sees that business. Read-only by default; tick “Also let it add customers and jobs” for one that can add. Turn a key off and it stops working on the next request.
Send it on every request as Authorization: Bearer gp_live_… (or an X-Api-Key: gp_live_… header). Everything is HTTPS and JSON. Times are ISO 8601 in UTC; each job also has scheduled_for_local and the business’s time_zone. Money is whole cents in fields ending _cents (48500 is $485.00).
Check a key
curl https://app.guidedpathhomeservices.com/api/v1/me \
-H "Authorization: Bearer gp_live_YOUR_KEY"Endpoints
Base address https://app.guidedpathhomeservices.com/api/v1. Lists are newest first and come back as { "data": [...], "has_more": true, "next_cursor": "…" }.
GET/api/v1/me
Which business this key belongs to. Zapier and Make can use it to test the connection.
Example response
{
"data": {
"business_id": "6f1c2a9e-3b7d-4e21-9c55-0a1b2c3d4e5f",
"business_name": "Zazo Electric",
"time_zone": "America/Detroit",
"key_name": "Zapier",
"can_write": true,
"api_version": "v1"
}
}GET/api/v1/customers
Your customers (archived ones are left out). Notes, crew notes and saved-card details are never included.
| limit | How many to return, 1–100 (default 25). |
| cursor | next_cursor from the page before, to get the next page. |
| created_since | Only records made at or after this ISO date-time, like 2026-11-01T00:00:00Z. |
| Only the customer with this email (any capitals). |
Example response
{
"data": [
{
"id": "2b9e7c41-5d3a-4f10-8e62-7a8b9c0d1e2f",
"name": "Jane Miller",
"contact_name": null,
"email": "jane@example.com",
"phone": "+19895550123",
"address_line1": "412 Oak St",
"address_line2": null,
"city": "Midland",
"state": "MI",
"zip": "48640",
"customer_type": "home",
"tags": [],
"lead_source": "Connected app",
"archived": false,
"created_at": "2026-11-03T14:05:11.482913+00:00"
}
],
"has_more": true,
"next_cursor": "eyJjIjoiMjAyNi0xMS0wM1QxNDowNTox…"
}GET/api/v1/customers/{id}
One customer, archived or not.
Example response
{
"data": {
"id": "2b9e7c41-5d3a-4f10-8e62-7a8b9c0d1e2f",
"name": "Jane Miller",
"contact_name": null,
"email": "jane@example.com",
"phone": "+19895550123",
"address_line1": "412 Oak St",
"address_line2": null,
"city": "Midland",
"state": "MI",
"zip": "48640",
"customer_type": "home",
"tags": [],
"lead_source": "Connected app",
"archived": false,
"created_at": "2026-11-03T14:05:11.482913+00:00"
}
}POST/api/v1/customers
Add a customer (needs a key that can add). If one of your customers already has that email, you get that customer back instead (200 with "created": false) and nothing is changed. A new one comes back as 201 with "created": true. US phone numbers are saved as +1 numbers; state is MI when left out.
| name | Required. The person or company. |
| contact_name | The person to talk to, for a company. |
| Optional; used to avoid duplicates. | |
| phone | Optional. |
| address_line1, address_line2, city, state, zip | Optional. |
Example request
curl -X POST https://app.guidedpathhomeservices.com/api/v1/customers \
-H "Authorization: Bearer gp_live_YOUR_KEY" \
-H "Content-Type: application/json" \
-d '{"name":"Jane Miller","email":"jane@example.com","phone":"989-555-0123","address_line1":"412 Oak St","city":"Midland","state":"MI","zip":"48640"}'Example response
{
"data": {
"id": "2b9e7c41-5d3a-4f10-8e62-7a8b9c0d1e2f",
"name": "Jane Miller",
"contact_name": null,
"email": "jane@example.com",
"phone": "+19895550123",
"address_line1": "412 Oak St",
"address_line2": null,
"city": "Midland",
"state": "MI",
"zip": "48640",
"customer_type": "home",
"tags": [],
"lead_source": "Connected app",
"archived": false,
"created_at": "2026-11-03T14:05:11.482913+00:00"
},
"created": true
}GET/api/v1/jobs
Your jobs, with who's on each one (archived jobs left out). Office notes, photos, recordings and sign-offs are never included.
| limit | How many to return, 1–100 (default 25). |
| cursor | next_cursor from the page before, to get the next page. |
| created_since | Only records made at or after this ISO date-time, like 2026-11-01T00:00:00Z. |
| status | scheduled, on_the_way, in_progress, no_answer, complete or cancelled. |
| from, to | Jobs whose date falls on these days (YYYY-MM-DD, the business's own calendar, both days included). |
| unscheduled | true for only jobs with no date yet. |
Example response
{
"data": [
{
"id": "8d4f1a23-9b6c-4e70-a1d2-3e4f5a6b7c8d",
"title": "Replace kitchen outlets",
"description": "Four outlets, two are GFCI.",
"status": "scheduled",
"job_type": "work",
"work_kind": "service",
"customer_id": "2b9e7c41-5d3a-4f10-8e62-7a8b9c0d1e2f",
"customer_name": "Jane Miller",
"customer_phone": "+19895550123",
"service_address": "412 Oak St",
"service_city": "Midland",
"service_zip": "48640",
"scheduled_for": "2026-11-05T14:30:00.000Z",
"scheduled_for_local": "2026-11-05T09:30",
"time_zone": "America/Detroit",
"duration_minutes": 120,
"on_hold": false,
"urgency": null,
"crew": [
{
"user_id": "4c2d8e10-7a3b-4f95-b1c6-2d3e4f5a6b7c",
"name": "Cameron Smith"
}
],
"completed_at": null,
"created_at": "2026-11-03T14:06:02.117004+00:00"
}
],
"has_more": true,
"next_cursor": "eyJjIjoiMjAyNi0xMS0wM1QxNDowNTox…"
}GET/api/v1/jobs/{id}
One job.
Example response
{
"data": {
"id": "8d4f1a23-9b6c-4e70-a1d2-3e4f5a6b7c8d",
"title": "Replace kitchen outlets",
"description": "Four outlets, two are GFCI.",
"status": "scheduled",
"job_type": "work",
"work_kind": "service",
"customer_id": "2b9e7c41-5d3a-4f10-8e62-7a8b9c0d1e2f",
"customer_name": "Jane Miller",
"customer_phone": "+19895550123",
"service_address": "412 Oak St",
"service_city": "Midland",
"service_zip": "48640",
"scheduled_for": "2026-11-05T14:30:00.000Z",
"scheduled_for_local": "2026-11-05T09:30",
"time_zone": "America/Detroit",
"duration_minutes": 120,
"on_hold": false,
"urgency": null,
"crew": [
{
"user_id": "4c2d8e10-7a3b-4f95-b1c6-2d3e4f5a6b7c",
"name": "Cameron Smith"
}
],
"completed_at": null,
"created_at": "2026-11-03T14:06:02.117004+00:00"
}
}POST/api/v1/jobs
Add a job with no date yet (needs a key that can add). It lands in “Needs a date” on the Jobs board for your office to book. Guided Path never texts or emails the customer about a job added this way; nothing goes out until someone books it in the app. Give a customer_id, or a customer object (found by email or added, same fields as POST /customers), or neither. With a customer and no address, the customer’s address is used. Every job added here is recorded in the business’s audit log with the key’s name.
| title | Required. What the work is. |
| description | Optional details. |
| type | estimate_visit (go look and price it; the default) or work (agreed work waiting for a date). |
| customer_id | One of your customers. |
| customer | Or { name, email, phone, address_line1, city, state, zip }. |
| service_address, service_city, service_zip | Where the work is, if not the customer's address. |
Example request
curl -X POST https://app.guidedpathhomeservices.com/api/v1/jobs \
-H "Authorization: Bearer gp_live_YOUR_KEY" \
-H "Content-Type: application/json" \
-d '{"title":"Replace kitchen outlets","description":"Four outlets, two are GFCI.","type":"work","customer_id":"2b9e7c41-5d3a-4f10-8e62-7a8b9c0d1e2f"}'Example response
{
"data": {
"id": "8d4f1a23-9b6c-4e70-a1d2-3e4f5a6b7c8d",
"title": "Replace kitchen outlets",
"description": "Four outlets, two are GFCI.",
"status": "scheduled",
"job_type": "work",
"work_kind": "service",
"customer_id": "2b9e7c41-5d3a-4f10-8e62-7a8b9c0d1e2f",
"customer_name": "Jane Miller",
"customer_phone": "+19895550123",
"service_address": "412 Oak St",
"service_city": "Midland",
"service_zip": "48640",
"scheduled_for": null,
"scheduled_for_local": null,
"time_zone": "America/Detroit",
"duration_minutes": 120,
"on_hold": false,
"urgency": null,
"crew": [],
"completed_at": null,
"created_at": "2026-11-03T14:06:02.117004+00:00"
},
"customer_created": false
}GET/api/v1/invoices
Your invoices (archived ones left out). public_url is the customer's own link, present once the invoice has been sent.
| limit | How many to return, 1–100 (default 25). |
| cursor | next_cursor from the page before, to get the next page. |
| created_since | Only records made at or after this ISO date-time, like 2026-11-01T00:00:00Z. |
| status | draft, sent, viewed, partial, paid or void. |
| customer_id | Only this customer's. |
Example response
{
"data": [
{
"id": "c3a1e5f7-2d4b-4c69-8a0e-1f2a3b4c5d6e",
"kind": "invoice",
"number": "INV1042",
"status": "partial",
"title": "Kitchen outlets",
"customer_id": "2b9e7c41-5d3a-4f10-8e62-7a8b9c0d1e2f",
"job_id": "8d4f1a23-9b6c-4e70-a1d2-3e4f5a6b7c8d",
"issue_date": "2026-11-05",
"due_date": "2026-11-19",
"expires_at": null,
"subtotal_cents": 45755,
"tax_cents": 2745,
"total_cents": 48500,
"amount_paid_cents": 20000,
"balance_cents": 28500,
"paid": false,
"sent_at": "2026-11-05T18:02:44.903321+00:00",
"first_viewed_at": "2026-11-05T19:10:02.551200+00:00",
"accepted_at": null,
"public_url": "https://app.guidedpathhomeservices.com/i/c3a1e5f7-2d4b-4c69-8a0e-1f2a3b4c5d6e?k=…",
"created_at": "2026-11-05T17:55:20.004512+00:00"
}
],
"has_more": true,
"next_cursor": "eyJjIjoiMjAyNi0xMS0wM1QxNDowNTox…"
}GET/api/v1/invoices/{id}
One invoice, with its lines and payments.
Example response
{
"data": {
"id": "c3a1e5f7-2d4b-4c69-8a0e-1f2a3b4c5d6e",
"kind": "invoice",
"number": "INV1042",
"status": "partial",
"title": "Kitchen outlets",
"customer_id": "2b9e7c41-5d3a-4f10-8e62-7a8b9c0d1e2f",
"job_id": "8d4f1a23-9b6c-4e70-a1d2-3e4f5a6b7c8d",
"issue_date": "2026-11-05",
"due_date": "2026-11-19",
"expires_at": null,
"subtotal_cents": 45755,
"tax_cents": 2745,
"total_cents": 48500,
"amount_paid_cents": 20000,
"balance_cents": 28500,
"paid": false,
"sent_at": "2026-11-05T18:02:44.903321+00:00",
"first_viewed_at": "2026-11-05T19:10:02.551200+00:00",
"accepted_at": null,
"public_url": "https://app.guidedpathhomeservices.com/i/c3a1e5f7-2d4b-4c69-8a0e-1f2a3b4c5d6e?k=…",
"created_at": "2026-11-05T17:55:20.004512+00:00",
"lines": [
{
"description": "Replace outlet with GFCI",
"quantity": 2,
"rate_cents": 12500,
"amount_cents": 25000,
"taxable": true,
"option_label": null,
"optional": false,
"selected": true
}
],
"payments": [
{
"id": "e1f2a3b4-c5d6-4e7f-8a9b-0c1d2e3f4a5b",
"amount_cents": 20000,
"tip_cents": 0,
"refunded_cents": 0,
"method": "check",
"paid_on": "2026-11-06",
"created_at": "2026-11-06T15:20:31.778120+00:00"
}
]
}
}GET/api/v1/estimates
Your estimates. The same fields as invoices, with expires_at and accepted_at; paid is always false.
| limit | How many to return, 1–100 (default 25). |
| cursor | next_cursor from the page before, to get the next page. |
| created_since | Only records made at or after this ISO date-time, like 2026-11-01T00:00:00Z. |
| status | draft, sent, viewed, accepted, declined, expired or converted. |
| customer_id | Only this customer's. |
Example response
{
"data": [
{
"id": "c3a1e5f7-2d4b-4c69-8a0e-1f2a3b4c5d6e",
"kind": "estimate",
"number": "EST0611",
"status": "accepted",
"title": "Kitchen outlets",
"customer_id": "2b9e7c41-5d3a-4f10-8e62-7a8b9c0d1e2f",
"job_id": "8d4f1a23-9b6c-4e70-a1d2-3e4f5a6b7c8d",
"issue_date": "2026-11-05",
"due_date": null,
"expires_at": "2026-12-05",
"subtotal_cents": 45755,
"tax_cents": 2745,
"total_cents": 48500,
"amount_paid_cents": 0,
"balance_cents": 48500,
"paid": false,
"sent_at": "2026-11-05T18:02:44.903321+00:00",
"first_viewed_at": "2026-11-05T19:10:02.551200+00:00",
"accepted_at": "2026-11-04T01:12:09.551200+00:00",
"public_url": "https://app.guidedpathhomeservices.com/i/c3a1e5f7-2d4b-4c69-8a0e-1f2a3b4c5d6e?k=…",
"created_at": "2026-11-05T17:55:20.004512+00:00"
}
],
"has_more": true,
"next_cursor": "eyJjIjoiMjAyNi0xMS0wM1QxNDowNTox…"
}GET/api/v1/estimates/{id}
One estimate, with its lines (and any deposit payments).
Example response
{
"data": {
"id": "c3a1e5f7-2d4b-4c69-8a0e-1f2a3b4c5d6e",
"kind": "estimate",
"number": "EST0611",
"status": "accepted",
"title": "Kitchen outlets",
"customer_id": "2b9e7c41-5d3a-4f10-8e62-7a8b9c0d1e2f",
"job_id": "8d4f1a23-9b6c-4e70-a1d2-3e4f5a6b7c8d",
"issue_date": "2026-11-05",
"due_date": null,
"expires_at": "2026-12-05",
"subtotal_cents": 45755,
"tax_cents": 2745,
"total_cents": 48500,
"amount_paid_cents": 20000,
"balance_cents": 28500,
"paid": false,
"sent_at": "2026-11-05T18:02:44.903321+00:00",
"first_viewed_at": "2026-11-05T19:10:02.551200+00:00",
"accepted_at": null,
"public_url": "https://app.guidedpathhomeservices.com/i/c3a1e5f7-2d4b-4c69-8a0e-1f2a3b4c5d6e?k=…",
"created_at": "2026-11-05T17:55:20.004512+00:00",
"lines": [
{
"description": "Replace outlet with GFCI",
"quantity": 2,
"rate_cents": 12500,
"amount_cents": 25000,
"taxable": true,
"option_label": null,
"optional": false,
"selected": true
}
],
"payments": []
}
}GET/api/v1/events
What happened lately, newest first, in exactly the shape webhooks send. Kept for 30 days.
| since | Only events after this ISO date-time. |
| event | Only this event, like job.completed. |
| limit, cursor | Paging, as above. |
Example response
{
"data": [
{
"id": "0a9b8c7d-6e5f-4a3b-9c2d-1e0f9a8b7c6d",
"event": "job.completed",
"created_at": "2026-11-05T18:02:45.120554+00:00",
"business_id": "6f1c2a9e-3b7d-4e21-9c55-0a1b2c3d4e5f",
"data": {
"id": "8d4f1a23-9b6c-4e70-a1d2-3e4f5a6b7c8d",
"title": "Replace kitchen outlets",
"status": "complete",
"job_type": "work",
"customer_id": "2b9e7c41-5d3a-4f10-8e62-7a8b9c0d1e2f",
"customer_name": "Jane Miller",
"service_address": "412 Oak St",
"service_city": "Midland",
"service_zip": "48640",
"scheduled_for": "2026-11-05T14:30:00+00:00",
"duration_minutes": 120,
"created_at": "2026-11-03T14:06:02.117004+00:00"
}
}
],
"has_more": true,
"next_cursor": "eyJjIjoiMjAyNi0xMS0wM1QxNDowNTox…"
}Webhooks
Add up to 10 webhook addresses under More → Connect other apps → Webhooks, each with the events it wants. Within about a minute of an event, Guided Path sends a POST with a JSON body:
{
"id": "0a9b8c7d-6e5f-4a3b-9c2d-1e0f9a8b7c6d",
"event": "invoice.paid",
"created_at": "2026-11-05T18:02:45.120554+00:00",
"business_id": "6f1c2a9e-3b7d-4e21-9c55-0a1b2c3d4e5f",
"data": {
"id": "c3a1e5f7-2d4b-4c69-8a0e-1f2a3b4c5d6e",
"kind": "invoice",
"number": "INV1042",
"status": "paid",
"title": "Kitchen outlets",
"customer_id": "2b9e7c41-5d3a-4f10-8e62-7a8b9c0d1e2f",
"job_id": "8d4f1a23-9b6c-4e70-a1d2-3e4f5a6b7c8d",
"total_cents": 48500,
"amount_paid_cents": 48500,
"balance_cents": 0,
"issue_date": "2026-11-05",
"due_date": "2026-11-19",
"sent_at": "2026-11-05T18:02:44.903321+00:00",
"accepted_at": null,
"created_at": "2026-11-05T17:55:20.004512+00:00"
}
}- Headers:
X-GuidedPath-Event(the event),X-GuidedPath-Delivery(this delivery’s id) andX-GuidedPath-Signature. - Answer with any 2xx within 10 seconds. Do slow work afterwards.
- No 2xx? We try again after 1 minute, 5 minutes, 30 minutes, 2 hours and 6 hours (6 tries in all), then give up on that event.
- Redirects aren’t followed: use the final address. After 20 failures in a row, or a 410 Gone answer, the webhook is switched off and the app shows why. Resume it there once it’s fixed.
- Very rarely the same event can arrive twice. Use the event
idto skip one you’ve already handled. - The address must be public https. Addresses on private networks are refused, checked again on every delivery.
- “Send a test” posts an event called
webhook.testwith a sample inside. It isn’t a real event and never shows up in /events.
Events
Events are recorded while the business has an API key or a webhook turned on, whichever way the change was made (in the app, online booking, the AI receptionist, a payment through Stripe or the API). Records brought in by an import don’t send events. Each event carries the record’s basic details only; use the API to read more.
customer.created
A new customer is added. Any new customer: added in the app, online booking, the AI receptionist, a lead you claimed or the API.
data
{
"id": "2b9e7c41-5d3a-4f10-8e62-7a8b9c0d1e2f",
"name": "Jane Miller",
"contact_name": null,
"email": "jane@example.com",
"phone": "+19895550123",
"address_line1": "412 Oak St",
"city": "Midland",
"state": "MI",
"zip": "48640",
"created_at": "2026-11-03T14:05:11.482913+00:00"
}job.created
A new job is added. Any new job, with or without a date.
data
{
"id": "8d4f1a23-9b6c-4e70-a1d2-3e4f5a6b7c8d",
"title": "Replace kitchen outlets",
"status": "scheduled",
"job_type": "work",
"customer_id": "2b9e7c41-5d3a-4f10-8e62-7a8b9c0d1e2f",
"customer_name": "Jane Miller",
"service_address": "412 Oak St",
"service_city": "Midland",
"service_zip": "48640",
"scheduled_for": null,
"duration_minutes": null,
"created_at": "2026-11-03T14:06:02.117004+00:00"
}job.scheduled
A job gets a date, or its date changes. The job's date and time is set for the first time or moved (not for complete or cancelled jobs). previous_scheduled_for is the old time, or null.
data
{
"id": "8d4f1a23-9b6c-4e70-a1d2-3e4f5a6b7c8d",
"title": "Replace kitchen outlets",
"status": "scheduled",
"job_type": "work",
"customer_id": "2b9e7c41-5d3a-4f10-8e62-7a8b9c0d1e2f",
"customer_name": "Jane Miller",
"service_address": "412 Oak St",
"service_city": "Midland",
"service_zip": "48640",
"scheduled_for": "2026-11-05T14:30:00+00:00",
"duration_minutes": 120,
"created_at": "2026-11-03T14:06:02.117004+00:00",
"previous_scheduled_for": null
}job.completed
A job is marked complete. The job's status becomes complete.
data
{
"id": "8d4f1a23-9b6c-4e70-a1d2-3e4f5a6b7c8d",
"title": "Replace kitchen outlets",
"status": "complete",
"job_type": "work",
"customer_id": "2b9e7c41-5d3a-4f10-8e62-7a8b9c0d1e2f",
"customer_name": "Jane Miller",
"service_address": "412 Oak St",
"service_city": "Midland",
"service_zip": "48640",
"scheduled_for": "2026-11-05T14:30:00+00:00",
"duration_minutes": 120,
"created_at": "2026-11-03T14:06:02.117004+00:00"
}estimate.sent
An estimate is sent to a customer. The first time the estimate is sent (by text, email or link).
data
{
"id": "5e7a9c1b-3d5f-4a82-b6c4-d8e0f2a4b6c8",
"kind": "estimate",
"number": "EST0611",
"status": "sent",
"title": "Kitchen outlets",
"customer_id": "2b9e7c41-5d3a-4f10-8e62-7a8b9c0d1e2f",
"job_id": "8d4f1a23-9b6c-4e70-a1d2-3e4f5a6b7c8d",
"total_cents": 48500,
"amount_paid_cents": 0,
"balance_cents": 48500,
"issue_date": "2026-11-05",
"due_date": null,
"sent_at": "2026-11-05T18:02:44.903321+00:00",
"accepted_at": null,
"created_at": "2026-11-05T17:55:20.004512+00:00"
}estimate.accepted
A customer accepts an estimate. Once per estimate: the customer signs it, or the office marks it accepted.
data
{
"id": "5e7a9c1b-3d5f-4a82-b6c4-d8e0f2a4b6c8",
"kind": "estimate",
"number": "EST0611",
"status": "accepted",
"title": "Kitchen outlets",
"customer_id": "2b9e7c41-5d3a-4f10-8e62-7a8b9c0d1e2f",
"job_id": "8d4f1a23-9b6c-4e70-a1d2-3e4f5a6b7c8d",
"total_cents": 48500,
"amount_paid_cents": 0,
"balance_cents": 48500,
"issue_date": "2026-11-05",
"due_date": null,
"sent_at": "2026-11-05T18:02:44.903321+00:00",
"accepted_at": "2026-11-04T01:12:09.551200+00:00",
"created_at": "2026-11-05T17:55:20.004512+00:00"
}invoice.sent
An invoice is sent to a customer. The first time the invoice is sent.
data
{
"id": "c3a1e5f7-2d4b-4c69-8a0e-1f2a3b4c5d6e",
"kind": "invoice",
"number": "INV1042",
"status": "sent",
"title": "Kitchen outlets",
"customer_id": "2b9e7c41-5d3a-4f10-8e62-7a8b9c0d1e2f",
"job_id": "8d4f1a23-9b6c-4e70-a1d2-3e4f5a6b7c8d",
"total_cents": 48500,
"amount_paid_cents": 0,
"balance_cents": 48500,
"issue_date": "2026-11-05",
"due_date": "2026-11-19",
"sent_at": "2026-11-05T18:02:44.903321+00:00",
"accepted_at": null,
"created_at": "2026-11-05T17:55:20.004512+00:00"
}invoice.paid
An invoice is paid in full. Once per invoice: payments reach the total.
data
{
"id": "c3a1e5f7-2d4b-4c69-8a0e-1f2a3b4c5d6e",
"kind": "invoice",
"number": "INV1042",
"status": "paid",
"title": "Kitchen outlets",
"customer_id": "2b9e7c41-5d3a-4f10-8e62-7a8b9c0d1e2f",
"job_id": "8d4f1a23-9b6c-4e70-a1d2-3e4f5a6b7c8d",
"total_cents": 48500,
"amount_paid_cents": 48500,
"balance_cents": 0,
"issue_date": "2026-11-05",
"due_date": "2026-11-19",
"sent_at": "2026-11-05T18:02:44.903321+00:00",
"accepted_at": null,
"created_at": "2026-11-05T17:55:20.004512+00:00"
}payment.received
A payment is recorded. Any payment recorded on an invoice or estimate deposit: card, check, cash, ACH or other.
data
{
"id": "e1f2a3b4-c5d6-4e7f-8a9b-0c1d2e3f4a5b",
"document_id": "c3a1e5f7-2d4b-4c69-8a0e-1f2a3b4c5d6e",
"document_kind": "invoice",
"document_number": "INV1042",
"customer_id": "2b9e7c41-5d3a-4f10-8e62-7a8b9c0d1e2f",
"job_id": "8d4f1a23-9b6c-4e70-a1d2-3e4f5a6b7c8d",
"amount_cents": 48500,
"tip_cents": 0,
"method": "card",
"paid_on": "2026-11-06",
"created_at": "2026-11-06T15:20:31.778120+00:00"
}Checking the signature
Each webhook has its own signing secret (Show signing secret, in the app). The header looks like t=1762300000,v1=5f2b…: t is when it was sent (Unix seconds) and v1 is the HMAC-SHA256, in hex, of the text t, a dot, and the raw body, using the secret as the key. Zapier and Make don’t need this; your own server should check it.
Node.js
import crypto from "node:crypto";
// rawBody: the request body exactly as received (a string, before JSON.parse).
// header: the X-GuidedPath-Signature header.
// secret: "whsec_…" from More → Connect other apps → Webhooks → Show signing secret.
export function verifyGuidedPath(rawBody, header, secret) {
const parts = Object.fromEntries(header.split(",").map((p) => p.split("=")));
const t = Number(parts.t);
// Refuse anything more than 5 minutes old (a replayed request).
if (!t || Math.abs(Date.now() / 1000 - t) > 300) return false;
const expected = crypto.createHmac("sha256", secret).update(`${parts.t}.${rawBody}`).digest("hex");
const a = Buffer.from(expected, "hex");
const b = Buffer.from(parts.v1 ?? "", "hex");
return a.length === b.length && crypto.timingSafeEqual(a, b);
}Rate limits and paging
Each key can make 120 requests a minute. Over that you get 429 with a Retry-After header (seconds). Every answer has X-RateLimit-Limit and X-RateLimit-Remaining.
Lists return up to limit records (default 25, at most 100), newest first. When has_more is true, ask again with cursor set to next_cursor and the same filters. Records added in the meantime don’t shift your pages.
Errors
Errors come back as { "error": { "code": "…", "message": "…" } } with a plain-English message.
| 400 | A filter, limit or cursor isn't right, or the body isn't JSON (invalid_limit, invalid_cursor, invalid_status, invalid_json…). |
| 401 unauthorized | No key, a wrong key, or a key that was turned off. |
| 403 read_only_key | This key can only read. Make one that can add. |
| 404 not_found | No record with that id in this business (or customer_not_found when adding a job). |
| 413 too_large | The body is over 64 KB. |
| 422 invalid_fields | A required field is missing or a value isn't right; the message names the fields. |
| 429 rate_limited | More than 120 requests in a minute. |
| 500 server_error | Our problem. Try again; it's been reported to us. |
Privacy
The business decides what it connects, and can turn any key or webhook off in one tap. Everything sent to another app (Zapier, Make, n8n or your own) is then handled under that app’s terms, not ours. Guided Path never sells your data. Read our privacy policy and help page.