Connecting a CRM
Patterns for HubSpot, Pipedrive, Salesforce and Zoho — won deal in, project status back out — plus accounting, calendars and automation platforms.
Updated 2 September 2026
Nearly every CRM request comes down to two flows, in one or both directions. This page gives the shape of each, then the specifics per platform. It assumes you have read the API page.
The two flows
Deal won → project created
A deal reaches Closed Won in the CRM. Something creates the matching BuildFlow project and invites the client.
- 1
Trigger
The CRM's own webhook on deal-stage change, or a poll of deals updated since your last run.
- 2
Map the fields
Deal name → project name, deal type → project_type, the property address → address fields, expected start → start_date, value → contract_sum.
- 3
Create the project
Call
create_project_from_templateso stages, tasks, milestones and inspections arrive together and consistent. - 4
Write the id back
Store the BuildFlow project id on the CRM deal in a custom field. Everything afterwards depends on this join.
- 5
Invite the client
Either through the app, or by creating an invite row and sending the link yourself.
Make it idempotent
Webhooks fire twice. Before creating, look for an existing project carrying that deal id — the reference column is there for your own job reference and works well as the join key.
Project status → back onto the CRM record
A scheduled job — hourly is plenty — reads the projects that have changed and updates the CRM.
GET /rest/v1/projects
?select=id,name,reference,status,completion_pct,target_end_date,updated_at
&company_id=eq.<company-id>
&updated_at=gte.2026-09-02T09:00:00ZPush status, completion_pct and target_end_date onto the deal or the linked project record. That alone answers most of what a sales or office team wants to know without them logging into anything.
Field mapping
| BuildFlow | Type | Typical CRM field |
|---|---|---|
name | text | Deal / opportunity name |
reference | text | Your job number — best join key back to the deal id |
project_type | enum | extension, kitchen, bathroom, loft_conversion, garage_conversion, roofing, landscaping, renovation, new_build, custom |
status | enum | planning, active, on_hold, snagging, completed, archived |
address_line1, city, postcode | text | Site address on the deal or the contact |
start_date, target_end_date | date | Expected start and completion |
contract_sum | numeric | The agreed sum, net of VAT. The client sees this on their Payments screen. |
completion_pct | 0–100 | A progress field, or a stage picklist |
HubSpot
- Out: a workflow on Deal stage = Closed Won, with a webhook action to your endpoint. Send the deal id and the properties you need.
- Back: create a custom deal property
buildflow_project_id(single-line text) and write it on creation. Addbuildflow_statusandbuildflow_progressfor the return sync. - In: the CRM API v3
PATCH /crm/v3/objects/deals/{dealId}updates those properties from your scheduled job. - Watch for: HubSpot workflows retry, so guard against duplicates on your side.
Pipedrive
- Out: Pipedrive webhooks on
updated.deal; check the new stage yourself, since the filter is coarse. - Back: a custom deal field for the project id — note that Pipedrive custom fields are addressed by a hash key, not the name you typed.
- In:
PUT /v1/deals/{id}for the status sync. - Watch for: the webhook payload carries both
currentandprevious; compare them so you only fire on the transition into Won, not on every edit afterwards.
Salesforce
- Out: an Outbound Message or a Flow with an HTTP callout on Opportunity stage change. Apex triggers give more control if you already have developer support.
- Back: a custom field
BuildFlow_Project_Id__con Opportunity. - In: the REST API
PATCH /services/data/vXX.X/sobjects/Opportunity/{id}. - Watch for: Salesforce needs the callout endpoint registered in Remote Site Settings, and it will not call an endpoint without a valid certificate.
Zoho CRM
- Out: a Workflow Rule with a Webhook action, or Deluge in a custom function if you want the logic inside Zoho.
- Back: a custom field on Deals for the project id.
- In:
PUT /crm/v6/Dealswith the record id. - Watch for: Zoho's OAuth tokens are region-specific — use the datacentre domain your account is on, not the .com default.
Automation platforms
If you would rather not run a service, all three of these will do the job with their generic HTTP step. There is no BuildFlow connector to click; you are configuring requests by hand.
| Platform | Use | Notes |
|---|---|---|
| Zapier | Webhooks by Zapier → Custom Request | Storage by Zapier can hold the access token between runs |
| Make | HTTP module, plus a Data Store for the token | Its scheduling is the most flexible of the three |
| n8n | HTTP Request node | Self-hostable, so the secret key can stay on your own infrastructure |
Token handling on a hosted platform
Access tokens last an hour. Either refresh at the start of each run, or store the refresh token and exchange it. Do not paste the secret key into a hosted automation platform — that hands your whole database to a third party.
Accounting and job costing
Xero, QuickBooks and Sage have no direct link, and BuildFlow holds the contract sum, the scope of works and the payment schedule, and nothing at ledger level. What is worth syncing is the other direction: project names and references, so invoices can be coded to a job that exists.
- Create the tracking category or job in the accounts package when the BuildFlow project is created, from the same trigger.
- Use
referenceas the shared key across all three systems. - Pull
statusback so finished jobs can be closed off rather than lingering.
Calendars
There is no iCal feed yet — it is on the shortlist. Until then, a scheduled job can read milestones and inspections and create events through the Google Calendar or Microsoft Graph API:
GET /rest/v1/milestones?project_id=eq.<id>&select=id,name,due_date,completed_at
GET /rest/v1/building_control_inspections
?project_id=eq.<id>&select=id,name,authority,scheduled_for,statusKeep the BuildFlow row id in the calendar event's extended properties so a rescheduled inspection updates the event instead of creating a second one.
Slack and Teams
Without webhooks this is a poll: every few minutes, ask for rows created since your last check and post the new ones to an incoming webhook.
GET /rest/v1/snag_items
?project_id=eq.<id>
&created_at=gte.<last-run-iso>
&select=id,title,room,status,reporter:profiles!snag_items_reported_by_fkey(full_name)
&order=created_at.ascStore the timestamp of the newest row you posted rather than the time you ran, or a slow write will be skipped.
Before you go live
- 1.Run it against a project you do not mind breaking. Every write in these recipes is real.
- 2.Give the integration its own user with the least role that works, so the audit trail shows what it did.
- 3.Handle 403 and 409 explicitly: the first means access, the second usually means a plan limit or a duplicate.
- 4.Log what you wrote, with the row ids. Reconstructing a bad sync without that is miserable.
- 5.Decide which system wins on a conflict, and write it down.