Boulevard rebooking and reviews automation works by subscribing to Boulevard Admin API webhooks, verifying the HMAC signature, and upserting clients and appointments into your CRM in real time. From there the CRM triggers rebooking nudges and review requests automatically on the right schedule. We built and shipped this for a multi-location med spa that wanted same-day follow-up without manual exports.
Boulevard automation for rebooking and reviews is the process of using Boulevard's Admin API and webhooks to sync clients and appointments into a CRM and trigger post-visit messaging automatically.
If you run a salon or med spa on Boulevard and want rebooking and reviews to run themselves, this guide shows how we wired it, what to watch out for, and the build steps we used in production.
The problem it solves
Most teams export a CSV from Boulevard reports, upload to a CRM, then try to remember who needs a rebooking nudge and who deserves a review request. Data gets stale, duplicates creep in, and messages go out late or not at all. The gap is worst the moment a guest checks out: that is when the clock starts on a timely ask.
- BrightLocal reported that 98 percent of consumers read online reviews for local businesses in 2024: source https://www.brightlocal.com/research/local-consumer-review-survey/
Here is the before and after on the exact workflow we replaced.
| Step | Manual process | Automated with Boulevard API |
|---|---|---|
| Data capture | Export Reports. Email as CSV. Download | Webhooks fire on appointment and client events in real time |
| Deduping | VLOOKUPs. Occasional mismatches | Idempotent upserts keyed by Boulevard IDs |
| CRM updates | Copy-paste fields into contacts and deals | Field-mapped writes to CRM with validation |
| Rebooking nudges | Staff remembers to text or call | Scheduled SMS or email sequences tied to visit date |
| Review requests | Someone sends links ad hoc | Post-visit flows send review requests on a delay |
| Reporting | Count rows in Sheets | CRM stages and tags power dashboards |
How the automation works
The architecture is simple on paper and strict in production: Boulevard emits signed webhooks. We verify and normalize the payload, upsert contacts and bookings into the CRM, then the CRM runs two automations: rebooking pathways and review requests. Nightly, we run a report-export backfill to reconcile anything missed and correct edge cases like reschedules.
- Boulevard Admin API base: https://dashboard.boulevard.io/api/2020-01/admin. Auth: HTTP Basic with an API key and a signed token you generate: docs https://developers.joinblvd.com/2020-01/admin-api/overview
- Webhooks are HMAC-signed with x-blvd-hmac-salt and x-blvd-hmac-sha256 headers: docs https://developers.joinblvd.com/2020-01/admin-api/guides/webhooks/
- Zapier and Make both exist, but Zapier does not support custom fields and requires Premier or Enterprise: docs https://help.zapier.com/hc/en-us/articles/8496057287565-How-to-Get-Started-with-Boulevard-on-Zapier
The core components we shipped:
- Boulevard webhooks: appointment and client lifecycle events come in as signed HTTP POSTs. We treat the headers as the auth boundary and reject anything that fails signature verification.
- Sync engine: a small Node service with a Postgres store. It deduplicates by Boulevard source IDs, maps fields to the CRM model, and writes with idempotency keys.
- CRM and segments: the CRM is the downstream system of record for outreach. We maintain segments for last-visit windows, new guests, and service categories to drive the right message.
- Messaging automations: two sequences run off CRM tags and dates: rebooking nudges and a spaced review request. The review request flow uses a short delay after checkout to catch guests while the visit is top-of-mind.
- Backfill and correction: a nightly Boulevard Report Export job pulls a signed CSV fileUrl and reconciles any missed or rescheduled appointments so the CRM timeline stays truthful.
Step-by-step: how to build it
1) Verify Boulevard webhook signatures first
Answer: receive Boulevard events on a dedicated endpoint and verify the HMAC using Boulevard's headers before touching any downstream system.
// server/webhooks/boulevard.js
import crypto from "crypto";
import express from "express";
const router = express.Router();
// Raw body parser required so signatures verify
router.post("/webhooks/boulevard", express.raw({ type: "application/json" }), (req, res) => {
const salt = req.header("x-blvd-hmac-salt");
const expected = req.header("x-blvd-hmac-sha256");
const secret = process.env.BOULEVARD_WEBHOOK_SECRET; // store securely
// Refer to Boulevard docs for the exact signing formula. A common pattern:
const computed = crypto
.createHmac("sha256", secret)
.update(Buffer.concat([Buffer.from(salt || ""), req.body]))
.digest("hex");
if (!salt || !expected || computed !== expected) return res.status(401).send("bad signature");
const event = JSON.parse(req.body.toString("utf8"));
// enqueue for processing, respond fast
queue.push({ source: "boulevard", event });
res.status(202).send("ok");
});
export default router;Gotcha: use an express.raw body parser on this route so you can hash the exact bytes Boulevard signed. JSON middleware will break verification.
2) Call the Admin API with Basic auth for backfills
Answer: some corrections are easier via API or a scheduled Report Export job. Build a small helper for authenticated Admin API requests.
// lib/blvd.js
import fetch from "node-fetch";
export function adminAuthHeader() {
const apiKey = process.env.BOULEVARD_API_KEY; // provisioned in Boulevard
const token = process.env.BOULEVARD_SIGNED_TOKEN; // you generate this per docs
const b64 = Buffer.from(`${apiKey}:${token}`).toString("base64");
return { Authorization: `Basic ${b64}` };
}
export async function blvdGraphQL({ query, variables }) {
const url = "https://dashboard.boulevard.io/api/2020-01/admin"; // GraphQL endpoint
const resp = await fetch(url, {
method: "POST",
headers: { ...adminAuthHeader(), "Content-Type": "application/json" },
body: JSON.stringify({ query, variables })
});
if (!resp.ok) throw new Error(`Boulevard ${resp.status}`);
return resp.json();
}Gotcha: the Admin API is GraphQL with cost-based rate limiting, so prefer a few well-structured queries over many chatty ones.
3) Normalize payloads and upsert to your CRM idempotently
Answer: model clients and appointments with stable source IDs and write idempotent upserts to the CRM.
// lib/sync.js
import { upsertContact, upsertAppointment } from "./crm.js";
export async function handleBoulevardEvent(evt) {
const { type, data } = evt; // e.g., "appointment.created"
// Minimal normalized record examples
if (type.startsWith("client.")) {
const contact = {
source: "boulevard",
source_id: String(data.id),
email: data.email || null,
phone: data.phone || null,
first_name: data.firstName,
last_name: data.lastName
};
await upsertContact(contact); // must be idempotent on (source, source_id)
}
if (type.startsWith("appointment.")) {
const appt = {
source: "boulevard",
source_id: String(data.id),
client_source_id: String(data.clientId),
starts_at: data.startAt,
ends_at: data.endAt,
location: data.locationName,
service_names: data.services?.map(s => s.name) || []
};
await upsertAppointment(appt);
}
}Gotcha: make the upsert key explicit. Use a unique constraint on (source, source_id) to prevent duplicates even if the webhook retries.
4) Trigger rebooking and review sequences from the CRM
Answer: let the CRM own messaging automation. Tag or stage-change on appointment completion, then schedule two flows: a gentle rebooking nudge and a single review request on a delay.
// lib/crm.js
import fetch from "node-fetch";
export async function upsertContact(contact) {
// Example generic CRM upsert with an idempotency key
const resp = await fetch(process.env.CRM_CONTACT_UPSERT_URL, {
method: "POST",
headers: {
"Content-Type": "application/json",
"Idempotency-Key": `blvd:${contact.source_id}`
},
body: JSON.stringify(contact)
});
if (!resp.ok) throw new Error(`CRM ${resp.status}`);
return resp.json();
}
export async function schedulePostVisitFlows({ contactId, visitAt }) {
// Queue jobs in your task runner for precise timing
await jobs.enqueue("rebooking_nudge", { contactId }, { delayMs: 3 * 24 * 3600 * 1000 });
await jobs.enqueue("review_request", { contactId }, { delayMs: 6 * 3600 * 1000 });
}Gotcha: keep review requests compliant with platform rules. Do not offer incentives or filter only happy guests.
5) Reconcile nightly with Report Exports
Answer: use Boulevard's Admin API Report Exports to fetch a signed CSV fileUrl and backfill anything missed or rescheduled.
// jobs/report-export.js
import { blvdGraphQL } from "../lib/blvd.js";
import { parse } from "csv-parse/sync";
import fetch from "node-fetch";
export async function nightlyReconcile() {
// Refer to Boulevard's Report Export guide for the exact mutation and fields
const { data } = await blvdGraphQL({ query: "mutation { /* reportExport */ }", variables: {} });
const fileUrl = data?.reportExport?.fileUrl; // signed URL for latest CSV
if (!fileUrl) return;
const csv = await (await fetch(fileUrl)).text();
const rows = parse(csv, { columns: true });
for (const r of rows) await reconcileRow(r);
}Gotcha: the fileUrl is signed. Download and process it promptly within the allowed window.
6) Enforce idempotency and monitor rate limits
Answer: store processed webhook IDs and guard your writes. Add small sleeps or batching if you see throttling.
-- db/schema.sql
create table processed_events (
id text primary key,
received_at timestamptz default now()
);
-- application logic (pseudo)
if (!exists(select 1 from processed_events where id = :eventId)) {
perform_sync();
insert into processed_events(id) values(:eventId);
}Gotcha: Boulevard's Admin API is cost-based. Fold child objects into a single well-formed GraphQL query rather than issuing many separate calls.
Where it gets complicated
- API access level: Boulevard restricts API and custom apps to Enterprise tier. Confirm plan and permissions early or you will stall after scoping: docs https://developers.joinblvd.com/getting-started/
- Zapier custom fields: the Zapier app does not support custom fields, and examples are generic rather than your account's real payload. If you rely on custom client attributes or service metadata, build custom.
- Timezones and locations: multi-location brands need per-location timezone handling so post-visit delays land at sane local times. Store timezone per location and schedule accordingly.
- Reschedules and partial visits: a rebook that becomes a no-show or a reschedule should not trigger duplicate or off-sequence messages. Your nightly reconcile should update CRM timelines, not add new ones.
- GraphQL query design: the Admin API is cost-limited. Collapse nested requirements in one query and cache stable reference data to reduce cost.
Boulevard to HubSpot or GoHighLevel: setup in practice
This is the minimal path we used for production rollouts while keeping Boulevard as the operational source of truth and the CRM as the messaging brain.
- Create external IDs: add a boulevard:clientId external identifier on Contacts and a boulevard:appointmentId on the appointment object you use.
- Map the write path: upsert contacts first, then appointments that reference the contact. Use an Idempotency-Key per write tied to the Boulevard ID.
- Tag for automation: on appointment completion, set last_visit_at, location, and a post_visit tag. Your CRM workflows trigger off these changes.
- Guard sends: at send time, re-check that no future appointment exists for the contact. Cancel the rebooking sequence automatically if a booking is detected.
- Audit trail: log the Boulevard event ID and the CRM record ID in a join table so support can trace any message back to the originating visit.
Tip: for GoHighLevel, prefer custom fields for last visit date, next due window, and location, then start workflows on field-change. For HubSpot, external IDs and a custom object for Appointments give you clean associations.
Boulevard Zapier vs custom code: when each fits
Teams ask if Zapier or Make can handle this. We shipped both patterns depending on scope.
- Use Zapier for: a single-location brand, basic review requests, and no custom fields. Accept generic samples and limited mapping.
- Use custom code for: multi-location routing, custom attributes, strict idempotency, and nightly reconciles. You own retries, rate handling, and audits.
- Middle path: Make.com can help for quick pilots. Be aware of quirks around auth, function helpers, and datastore writes. We documented tradeoffs in our comparison of Make vs n8n vs Google Apps Script.
Which CRMs work best and how we map the data
We shipped this with mainstream CRMs. The pattern is consistent: make Boulevard the source of truth for visit data and keep messaging logic inside the CRM.
- HubSpot: contacts keyed by boulevard:clientId. Appointments map to deals or a custom object. Use an external id property and an Idempotency-Key header on writes.
- GoHighLevel: contacts keyed by boulevard:clientId. Use custom fields for last visit date, next due window, and location. Trigger workflows on field change.
- Salesforce: contacts keyed by boulevard:clientId. Appointments map to Events or a custom Appointment object with a master-detail to Contact.
Minimum field mapping we have used in production:
- Contact: clientId, first name, last name, email, phone, opted-in status, location.
- Appointment: appointmentId, clientId, start, end, services, provider, location, status.
Tip: never dedupe by email alone. Key on Boulevard IDs and let your CRM dedupe logic link emails and phones later.
How we time rebooking and review messages
Getting timing right mattered more than copy. What worked for the med spa:
- Review request: send once, 4 to 8 hours after checkout. Respect quiet hours by scheduling within 9am to 7pm local time.
- Rebooking nudges: first touch at 3 days, second touch at 10 to 14 days if no booking is detected. Cancel the sequence if a new appointment appears.
- Multi-location: store location timezone and preferred send windows per location. Convert appointment time to local time before scheduling jobs.
We keep a small scheduler that checks conditions at send time: contact still active, no future booking on file, correct location, and not recently contacted.
// jobs/schedule-windows.js
export function nextWindow(ts, tz, startHour = 9, endHour = 19) {
const d = new Date(ts.toLocaleString("en-US", { timeZone: tz }));
d.setHours(Math.min(Math.max(d.getHours(), startHour), endHour), 0, 0, 0);
if (d.getHours() >= endHour) d.setDate(d.getDate() + 1), d.setHours(startHour);
return new Date(d.toLocaleString("en-US", { timeZone: "UTC" }));
}Review links per location without gating
Never gate reviews. We route guests to the correct review page per location using a simple lookup and a single compliant message.
// lib/review-links.js
const reviewLinks = {
"Downtown": "https://g.page/r/abcd",
"Uptown": "https://g.page/r/efgh"
};
export function linkFor(location) {
return reviewLinks[location] || "https://yourdomain.com/review";
}In the CRM template we insert the per-location link and avoid language that asks only happy guests to respond. If a guest replies with an issue, route to a service recovery inbox first.
What this actually changes
For a multi-location med spa, the production system turned front-desk handoffs into finished follow-ups: new and returning guests entered the CRM without staff steps, rebooking nudges landed a few days after the visit, and review requests went out the same day. Staff stopped exporting CSVs and fixing duplicates. The inbox saw steadier, on-brand replies without someone babysitting a send list.
- BrightLocal found that 98 percent of consumers read online reviews for local businesses in 2024. Automating timely review requests matters because guests cannot act on a request that never arrives: source https://www.brightlocal.com/research/local-consumer-review-survey/
We designed the system so the CRM remains your outreach source of truth. Boulevard stays focused on operations. The line between them is a verified, idempotent sync that you can audit and backfill.
Frequently asked questions
Does Boulevard have an official API?
Yes. Boulevard exposes an Admin API for merchant operations and a Client API for the booking storefront. Authentication uses HTTP Basic with an API key and a signed token you generate. Details: https://developers.joinblvd.com/2020-01/admin-api/overview
What Boulevard plan do I need for API access?
API access and custom apps are limited to Enterprise tier customers. The official Zapier app requires Premier or Enterprise and admin permissions. Plan this before development so you do not hit a permission wall mid-build.
Can this sync in real time or only by CSV?
Real time via webhooks. Boulevard webhooks are HMAC-signed and retry on failure. We verify signatures, upsert idempotently, and still run a nightly Report Export reconcile for safety and reschedules.
Can I do this with Zapier or Make instead of custom code?
You can for simple flows. Limits apply: Zapier does not support custom fields and samples are generic. For multi-location mapping, custom attributes, and idempotency, a small Node service gives you control and auditability.
How do you prevent duplicate contacts or double-sends?
We key everything on Boulevard source IDs with a database unique constraint and idempotency keys on CRM writes. Webhook retries are safe, and reschedules update the same timeline rather than spawning new ones.
What does this cost monthly to run?
Infrastructure is light: a small Node service, a Postgres instance, and your CRM. The largest costs tend to be engineering time to build it right and your CRM or messaging credits. Boulevard API rate limits exist, so batch wisely.
If you want the rebooking and review loop to run itself while Boulevard stays your operational source of truth, we have shipped this exact integration. See related builds like our iClassPro to GoHighLevel sync guide, how we automate Google review replies, and our playbook to automate CRM lead follow-up. Explore our CRM automation services and book a short discovery call at /book.
Want us to build this for you?
Nine questions, about 90 seconds. You see the hours it is costing you, then pick a time. No pitch.
Get your free assessment