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.
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 our other CRM builds like the iClassPro to GoHighLevel sync in our blog. If you are ready to scope yours, see our services for CRM automation at /services#crm-automation, read our related post on automate-iclasspro-to-gohighlevel, and book a short discovery call at /book.
Want us to build this for you?
15-minute discovery call. No pitch. We tell you what to automate first.
Book a Discovery Call