CRM for Staffing Agency: Two-Sided Data Model, ATS Boundary Decisions and Margin Reporting
CRM for staffing agency design: the two-sided market, which records the ATS owns, how placement margin differs from markup, and when to build custom objects.
Paul Maxwell, PhD
AUTHOR
GET WEEKLY REVOPS INSIGHTS
No spam. Unsubscribe anytime.
A CRM for staffing agency work is bought to run business development with client companies, and it is then asked to report on placements it does not own. Two defects follow. The margin dashboard shows a figure finance does not recognise, because it was calculated from a bill rate and a pay rate copied out of the applicant tracking system, and those rates omit every employer cost that payroll carries. The contact list stops matching the people the agency knows, because the tracking system stores a hiring manager and a candidate as different entities while the CRM stores a person once.
This article sets out how a staffing or recruiting agency divides its records between a CRM and an applicant tracking system. It covers the two-sided market, the division of records and the identity rule for people on both sides, then works one sample job order through three figures called margin. HubSpot's custom objects, a comparison with an ATS used alone, a sync procedure, symptoms, evidence limits and common questions follow.
An applicant tracking system (ATS) is the system in which candidates are sourced, submitted and placed; Bullhorn is the example here because its REST API entity reference is public. A job order is a client's request to fill one or more openings, a submission puts a candidate forward for it, and a placement records a candidate filling it. Burden is the employer's cost of a worker beyond the pay rate: payroll taxes, unemployment insurance, workers' compensation and benefits. A system of record is the one system permitted to write a given field.
The Two-Sided Market Behind a Staffing Agency CRM
A staffing agency sells into two markets at once: client companies supply demand as job orders, and candidates supply the labour that fills them. A sales CRM built around one buyer and one deal models only the first, and Bullhorn's reference documents where the two meet. A JobSubmission references both the candidate and the job order, and an approved submission creates a placement. Each Placement corresponds to a single job order and a single candidate, neither changeable after creation, while one job order may have several placements.
Every figure an agency owner asks for crosses that record. Fill rate divides placements, created on the candidate side, by openings, which the JobOrder holds as numOpenings on the client side. Margin by client joins the client's bill rate to the worker's pay rate on the placement. A CRM holding only the client side can report neither without a copy of the placement.
The same reference documents client-side entities too: an Opportunity can be converted to a job order and carries a deal value and an expected close date, and a Lead can become a candidate, a client contact or a client company. An ATS of this kind already runs a business-development pipeline, so a separate CRM has to be justified by what that pipeline lacks for a particular agency.
The finding that organises this article is that the boundary between a staffing CRM and the ATS is set record by record rather than team by team, and that two records decide it. The first is the person. Bullhorn can hold one person as a candidate and a client contact joined by a linkedPerson field, while HubSpot holds one contact keyed on email, so a sync that ignores the link creates two contacts wherever the two ATS records carry different addresses. The second is the placement, the record on which the two markets transact and the only one on which Bullhorn's reference records a margin net of burden. A CRM that derives margin from the two synced rates reports spread instead, which on the sample below overstates gross profit by 47 percent.
Record Ownership Between a Staffing CRM and the ATS
Three questions arrive as one when an agency adds a CRM: which system each team works in, which system writes each record, and which system computes the figures the agency is managed by. A recruiter works in the ATS and an account manager in the CRM, yet a client company is written by both unless its fields are assigned, and neither computes a burdened margin without payroll data.
| Record | System of record | What the CRM holds |
|---|---|---|
| RecordProspect company and its contacts | System of recordCRM | What the CRM holdsThe full record, with marketing history |
| RecordBusiness-development deal for a master services agreement or supplier status | System of recordCRM | What the CRM holdsThe deal, in its own pipeline |
| RecordClient company once a job order exists | System of recordCRM for identity; ATS for billing terms and fee arrangement | What the CRM holdsIdentity fields, with the ATS ID in a unique-value property |
| RecordClient contact: hiring manager, timesheet approver, billing contact | System of recordCRM for identity; ATS for roles on job orders and placements | What the CRM holdsThe contact, typed as a client contact |
| RecordJob order | System of recordATS | What the CRM holdsA read-only copy, or counts rolled up to the company |
| RecordCandidate | System of recordATS | What the CRM holdsA contact only where the agency markets to that person |
| RecordSubmission | System of recordATS | What the CRM holdsCounts only |
| RecordPlacement | System of recordATS | What the CRM holdsA read-only copy: dates, status, rates and burdened margin |
| RecordTimesheets, pay and burden | System of recordPayroll or back office | What the CRM holdsPeriod totals on the placement, at most |
The deal is the sale of the relationship, and a job order is a request made under it. HubSpot's custom objects documentation states that sales forecasts and deal attribution reports connect directly to deals and not to other objects, so a job order written as a deal enters the forecast as new business, while one held elsewhere leaves the forecast describing client relationships alone.
The client company is the one record both systems legitimately write. Identity and account ownership go to the CRM and billing terms to the ATS, because Bullhorn's ClientCorporation holds the billing frequency a new placement copies and the fee percentage the client pays per placement.
Person Records Across the ATS and the CRM
Bullhorn keeps candidates and client contacts as separate entities with separate IDs, and its ClientContact reference notes that a candidate may become a client contact after being placed, or a client contact may become a candidate. One person can hold two ATS records at once, and Bullhorn joins them: the Candidate and ClientContact entities each carry a linkedPerson field that holds the other record's ID when the person is both.
HubSpot stores that person once, because it deduplicates contacts by email address. Its custom objects documentation also advises against an object that replicates contacts, citing a school whose parents and teachers might each become one: a person can be both, so the documentation recommends a contact type property instead. The staffing case is the same case with different labels.
Each person is therefore one HubSpot contact with a multiple-checkbox contact type holding Candidate, Client contact or both, and two unique-value properties for the ATS candidate ID and the ATS client contact ID. HubSpot allows up to ten unique-value properties per object, set only when a property is created, so both must exist before the first load. A sync resolves each person by the record's own ATS ID, then by the ID in linkedPerson, then by email, creating a contact only when all three lookups fail. The middle lookup carries the weight, because the two ATS records need not share an address. A client contact held under a work email and the same person held as a candidate under a personal one would otherwise become two contacts, which email deduplication cannot join.
Companies need a lookup for a different reason: the deduplication documentation states that companies created through the API, including by third-party sync apps, are not deduplicated by domain, so a unique ATS client corporation ID is the key. The wider problem is treated in HubSpot deduplication across three systems.
Candidates also raise a cost question. Only marketing contacts count toward the contact tier, and bulk marketing email reaches contacts only (custom objects documentation), so a candidate receiving job alerts must be a marketing contact while one receiving nothing can stay non-marketing, at no charge, or stay out of the CRM.
Placement Margin on Sample Data: Markup, Spread and Gross Margin
Bullhorn's placement carries two figures called margin. markUpPercentage is documented as (clientBillRate − payRate) ÷ payRate, stored as a fraction, and reportedMargin as hourly margin in dollars, calculated using burden and other costs. A CRM that copies the two rates and derives margin in a calculation property rebuilds the figure without burden, whatever the property is named.
Part of burden is statutory. An employer pays 6.2 percent of wages for Social Security and 1.45 percent for Medicare, 7.65 percent together, with Social Security applying up to a 2026 wage base of $184,500 (IRS Topic 751). Unemployment insurance, workers' compensation and benefits sit on top, at rates held in payroll records; Bullhorn documents a WorkersCompensationRate entity for the second.
The figures below are sample data, built so the arithmetic can be rechecked, and describe no client. A job order asks for four contract analysts at a bill rate of $62.50 an hour and a pay rate of $40.00. Eleven submissions produce three placements, each working 480 hours in the quarter: twelve weeks at forty hours. Burden is 18 percent of pay, of which 7.65 points are statutory and 10.35 points stand in for insured and benefit costs; at $83,200 for a 2,080-hour year, pay stays below the wage base.
| Measure | Per hour | Quarter, 1,440 hours |
|---|---|---|
| MeasureBill rate and revenue | Per hour$62.50 | Quarter, 1,440 hours$90,000 |
| MeasurePay rate and wages | Per hour$40.00 | Quarter, 1,440 hours$57,600 |
| MeasureSpread: bill less pay | Per hour$22.50 | Quarter, 1,440 hours$32,400 |
| MeasureBurden at 18% of pay | Per hour$7.20 | Quarter, 1,440 hours$10,368 |
| MeasureGross profit | Per hour$15.30 | Quarter, 1,440 hours$22,032 |
| Figure | Formula | Sample value |
|---|---|---|
| FigureMarkup, Bullhorn's markUpPercentage | Formula(bill − pay) ÷ pay | Sample value56.25% |
| FigureSpread margin | Formula(bill − pay) ÷ bill | Sample value36.00% |
| FigureGross margin | Formula(bill − pay − burden) ÷ bill | Sample value24.48% |
The spread of $32,400 exceeds the gross profit of $22,032 by exactly the burden, $10,368, so a dashboard built on spread reports 47 percent more profit than the quarter produced and a margin 11.52 points higher. Neither system is malfunctioning: the CRM has computed a different quantity under the name finance uses. The rule is to copy reportedMargin, or payroll's burdened figure, onto the CRM's placement record, and to label anything derived from two rates as spread.
The client side gives the fill figures: three placements against four openings is a fill rate of 75 percent, at 3.67 submissions per placement, and the unfilled opening is $30,000 of revenue and $7,344 of gross profit the quarter did not earn. Hours worked come from timesheets, which Bullhorn documents as a Timesheet record aggregating time entries for payroll, so a CRM multiplying bill rate by scheduled hours reports a plan rather than an invoice.
Custom Objects and ATS-Owned Records in HubSpot
Holding copies of job orders or placements as HubSpot records requires custom objects, which the custom objects documentation lists for Enterprise subscriptions only, in every hub, with object and property counts limited by subscription. They are created under Data Management > Data Model, and an object's internal name, which integrations use, cannot be edited afterwards. A Professional portal therefore receives rolled-up figures on the company instead: open job orders, active placements and gross profit for the quarter.
On Enterprise, record-level copies earn their place where a report needs that grain, such as gross profit by client and recruiter filtered by placement status. Association labels, created by a Super Admin at up to 50 per object pair, record each contact's role on a placement, which matters because Bullhorn's placement names an approving client contact and a billing client contact as well as the candidate. The calculation property documentation allows a rollup to be limited to selected association labels and to conditions on number and enumeration properties, so a company can sum gross profit from its active placements alone.
Copied fields can be closed to editing on Enterprise. A property restriction set to allow everyone to view leaves edits to Super Admins, and a pay rate can be hidden from users outside an access list. HubSpot's restriction documentation states the limit: any user can still set a restricted property through the API or when creating a record by hand. HubSpot field-level permissions treats the feature as a governance control on that evidence. The copy stays true because the sync rewrites copied fields on every event from the ATS, and the restriction prevents casual edits between events.
Where no ATS exists and HubSpot holds job orders, placements and timesheets itself, the object model is set out in HubSpot for staffing and recruiting, and the margin rule still applies: burden must be a property on the placement before a calculated margin deserves the name. A staffing agency's contract-generation build generated documents from a job order object with placements beneath it, a case that needed record-level grain.
Costs and Returns of a Separate Staffing CRM
The firm writing this article is a HubSpot Solutions Partner, which is an interest in the answer that adds a CRM; the conditions below name where the ATS used alone is the better choice.
A separate CRM buys a client-side record built for business development: marketing email and segmentation on contacts, a pipeline for relationships rather than requisitions, and client-level gross profit beside that pipeline. It costs a sync with an identity rule, a second licence, Enterprise tier for record-level copies, upkeep whenever either vendor changes its API, and the risk of a margin computed in the wrong place.
The ATS used alone wins on two specific axes. The first is a single database: Bullhorn documents Opportunity and Lead in the same data model as JobOrder and Placement, so an opportunity becomes a job order with no sync, no identity rule and no second copy of the client. The second is delivery depth: the same reference documents Timesheet, WorkersCompensationRate, CandidateCertification and PlacementCertification entities, each of which HubSpot would need as an Enterprise custom object.
| Dimension | ATS used alone | ATS beside a general CRM |
|---|---|---|
| DimensionBusiness-development pipeline | ATS used aloneOpportunity converts to a job order in one database | ATS beside a general CRMA deal pipeline for relationships, with client identity synced to the ATS |
| DimensionPerson identity | ATS used aloneCandidate and client contact joined by `linkedPerson` | ATS beside a general CRMOne contact holding both ATS IDs, resolved by the sync |
| DimensionMargin reporting | ATS used alone`reportedMargin` on the placement | ATS beside a general CRMA copy of `reportedMargin`, or a derived figure labelled spread |
| DimensionTimesheets, certifications and workers' compensation | ATS used aloneDocumented ATS entities | ATS beside a general CRMLeft in the ATS, or copied as Enterprise custom objects |
| DimensionAdded cost | ATS used aloneNone beyond the ATS | ATS beside a general CRMA second licence, sync upkeep and Enterprise tier for record-level copies |
The separate CRM's case is strongest where client acquisition is a marketing-led motion across many prospect companies that have never raised a job order, and leadership wants gross profit by client beside the pipeline that wins them. It is weakest for a small permanent-placement agency, where Bullhorn's placement carries a fee and an empty end date and no hours need reconciling, and wherever the ATS's opportunity pipeline is already trusted. The patterns that shape the sync are set out in the HubSpot integration architecture paper.
Sync Procedure and Reconciliation for a Staffing CRM
- Write the ownership table for the agency's own records, field by field, naming the one system that writes each field and the direction each copy travels.
- In HubSpot, create the unique-value properties before any load: the ATS client corporation ID on companies, the ATS candidate ID and ATS client contact ID on contacts, and an ATS ID on each custom object.
- Create a contact type property as multiple checkboxes, with Candidate and Client contact as options.
- On Enterprise, create the job order and placement objects under Data Management > Data Model, confirm each internal name before saving, and create association labels for the candidate, approving contact and billing contact.
- In Bullhorn, create an entity event subscription for ClientCorporation, ClientContact, Candidate, JobOrder and Placement with the event types INSERTED, UPDATED and DELETED, and read the queue on a schedule.
- Write each copy in its assigned direction only, resolving every person by the record's own ATS ID, then the ID in
linkedPerson, then email, and every company by ATS client corporation ID, before creating anything. - Read
isDeletedon every UPDATED event for candidates, client contacts and job orders, and treat a true value as a removal. Bullhorn documents a soft delete as an update that sets that flag, and all three entities carry it. - Copy the burdened margin onto the placement; where the deployment does not populate
reportedMargin, name the derived property Spread. - Verify against a closed period: placement counts by status agree between the systems; gross profit summed in the CRM matches the payroll figure; five people known to be both candidate and client contact each resolve to one HubSpot contact carrying both IDs; a test candidate soft-deleted in the ATS shows as removed in the CRM; and no two sync-created companies share a domain. Any difference no documented rule explains is a defect in the sync.
Staffing CRM Symptoms and the Mechanisms Behind Them
The CRM reports a higher margin than finance. It derived margin from bill rate and pay rate, which yields spread: 36.00 percent against a gross margin of 24.48 percent on the sample. The repair copies the burdened figure and renames the derived one.
Client companies appear twice after the first sync. Companies created through the API are not deduplicated by domain, so each ATS client corporation arrived as a new company; a unique ATS ID property and a lookup before every create prevent the repeat.
One person appears as two contacts. The sync resolved people by their own ATS ID and by email, and the candidate record and the client contact record carried different addresses. Resolving through linkedPerson before the email lookup joins them.
A candidate removed from the ATS remains in the CRM. Candidates are soft-deleted, and Bullhorn documents the soft delete as an update that sets isDeleted, so a sync that removes records only on a DELETED event can miss it. Reading the flag on every update closes the gap.
Placements stay active in the CRM after the ATS removed them. The subscription listed INSERTED and UPDATED but not DELETED, so removals never reached the queue.
The deal forecast grows with every requisition. Job orders are being written as deals in the business-development pipeline, where the forecast counts them as new business.
The marketing contact tier rises after candidates are synced. The sync set every candidate as a marketing contact, although only those receiving marketing email need that status.
Boundaries of the Staffing CRM Evidence
This article covers contract and temporary staffing, where placements accrue hours; permanent placement appears only where its fee model moves the boundary, and vendor management system programmes, payroll design and credential verification are outside it. Other ATS platforms were not assessed, and nothing here claims that a maintained HubSpot–Bullhorn connector does or does not exist, a question the two vendors' marketplaces answer on the day of purchase. Product facts are as documented in September 2026: the HubSpot pages cited were last updated between 2 April and 25 September 2026, and the Bullhorn reference was read on 27 September 2026.
The evidence has four limits. Bullhorn documents what reportedMargin means but not which deployments populate it or how burden is configured, so the copy rule is a specification to test. It documents a soft delete as an update but does not name the event type the queue then carries, so the isDeleted rule is defensive rather than observed. The 18 percent burden is a sample figure beyond its 7.65 percent statutory part. No source consulted compares outcomes for agencies with and without a separate CRM, so the costs and returns describe mechanisms rather than measured effects.
Staffing Agency CRM Questions
Is there a staffing agency CRM for enterprise clients?
An enterprise client adds depth on the client side: divisions, several approvers and negotiated billing terms. Bullhorn's ClientCorporation documents parent and child corporations and a fee arrangement, and HubSpot's association labels support paired labels such as parent and child. The billing terms stay in the ATS, which invoices from them, while the CRM maps the relationship.
Which are the top platforms for staffing agency CRM?
The platforms fall into three architectures rather than a ranking: an ATS with client-side entities, used alone; an ATS beside a general CRM such as HubSpot; and a general CRM with custom objects standing in for the ATS. No source consulted compares outcomes across them, so the conditions in the costs section decide.
Does staffing CRM software for agency recruiters replace the ATS?
Only when the CRM itself holds candidates, submissions and placements. Beside an ATS, the CRM is the account manager's system, and recruiters read client history from it.
Which is the best CRM for small staffing agency teams?
With few client accounts, a second system adds an identity rule and a reconciliation to serve a small client side, which points to one system: an ATS with client-side entities where recruiting dominates, or a general CRM with Enterprise custom objects where marketing to clients dominates.
Which is the best CRM for a medical staffing agency?
Credentialing weighs heavily. Bullhorn documents CandidateCertification and PlacementCertification entities, the second holding the certifications a placement requires, so licence checks sit in the ATS, and a CRM beside it keeps to facility contacts and the pipeline for new facilities.
In Summary
A staffing agency sells into two markets that transact through the placement, and a CRM beside an ATS works when every field has one writer: the CRM writes prospects, relationships and client identity; the ATS writes job orders, candidates, submissions, placements and billing terms; payroll writes hours, pay and burden.
Two records decide that boundary. A person can be a Bullhorn candidate and client contact at once, joined by linkedPerson, while HubSpot stores one contact keyed on email, so both ATS IDs need unique properties and the sync resolves through the link before it tries email. The placement carries markup on pay and an hourly margin with burden, and on the sample the spread of 36.00 percent sits 11.52 points above a gross margin of 24.48 percent. Custom objects for these records are an Enterprise feature worth building only where a report needs record-level grain. On every tier, the CRM's margin is copied from the burdened figure or labelled spread, so a board reading gross profit reads the $15.30 an hour the agency keeps, not the $22.50 it bills above pay.