Custom SIS-to-HubSpot Integration for a Health Sciences University's Admissions Pipeline
A private chiropractic and health-sciences university ran its admissions pipeline through a Student Information System with no published API and no native HubSpot connector, so every applicant record had to be reconciled by hand. Salted Stone built a bidirectional integration on AWS Lambda and API Gateway keyed on the SIS's own identifiers rather than email, since one contact can carry more than one enrollment record. A property change on the HubSpot Contact fires the sync toward the SIS in near real time, and an hourly poll pulls updates back.
Executive Summary
Context
The university enrolls students into chiropractic and allied health programs and runs admissions through HubSpot Sales Hub, but its Student Information System, Anthology's Campus Nexus Student, had never exposed an API or a connector for any CRM. Every applicant's enrollment status lived in one system while every interaction with that applicant lived in another, with no supported path to keep the two in sync.
What We Built
We built a bidirectional AWS Lambda and API Gateway integration that pushes a HubSpot Contact to the SIS the moment its program status changes to Applicant, and pulls SIS updates back on an hourly poll, replacing a manual, dual-entry admissions workflow with one governed sync.
Tech Stack
- AWS Lambda
- AWS API Gateway
- AWS CloudWatch
- HubSpot webhooks (contact.propertyChange)
- Anthology Campus Nexus Student SIS
- Slack
Not a fit if there is no internal owner ready to take on the sync after launch. A bidirectional build against an SIS with no published API is reverse engineered against a live vendor's private data model, and it needs someone on the Admissions or IT side monitoring the CloudWatch alerts and owning fixes when the vendor changes something upstream, not a system that runs itself indefinitely once it ships.
The Challenge
Admissions counselors tracked each applicant's program status inside HubSpot, but Campus Nexus Student held the enrollment record of truth, and Anthology, the SIS vendor, had never published an API or an integration path. Every time a prospect moved from applicant to enrolled, a staff member checked both systems by hand and updated whichever one had not caught up, and because a single contact could hold more than one SIS enrollment, matching on email alone produced false matches. As admissions volume grew, the two systems drifted further apart between manual checks, and a stale status left a counselor working from the wrong picture of where an applicant stood.
Our Approach
The team first looked for a native connector between HubSpot and Campus Nexus Student, but Anthology had never published one or handed over API documentation, so an off-the-shelf integration was never an option. Salted Stone built custom middleware on AWS Lambda and API Gateway instead, one of the custom API layers an undocumented vendor leaves you to write, with CloudWatch as the logging layer. The sync triggers when a Contact's program_status_stage changes to Applicant, firing a webhook that pushes the record to the SIS in near real time. Rather than match on email, which one contact could hold more than once across separate SIS enrollments, the integration matches on SIS-issued identifiers, sis_id and adenroll_id, on both the Contact and a custom Program Status object. The return path runs on an hourly poll instead, pulling only SIS records modified in the past hour, a trade against real-time sync for a build that did not depend on the SIS exposing anything it was not built to expose. On failure, the middleware halts, logs to CloudWatch, and alerts the development team by email and Slack.
Impact
Admissions no longer reconciles two systems by hand
Counselors used to check both HubSpot and the SIS separately whenever an applicant's status might have moved, because nothing kept the two in sync automatically. The bidirectional build removed that step: a property change on the Contact now pushes to the SIS on its own, and the hourly return poll brings updates back without anyone re-entering them, so staff time goes to admissions work rather than reconciliation.
Applicant status updates without a manual check
Before the integration, an applicant's move to enrolled or withdrawn could sit unreflected in HubSpot until someone checked the SIS by hand. Now the webhook on program_status_stage fires the moment that stage changes to Applicant, so the record Admissions works from reflects the SIS side within the hour instead of whenever staff time allowed.
Multiple enrollments per contact no longer break the match
Matching on email alone would have merged or missed records whenever a single contact held more than one SIS enrollment, which happens whenever a prospect applies to more than one program. Keying the sync on SIS-issued identifiers, sis_id and adenroll_id, on both the Contact and the Program Status object instead means each enrollment resolves to the right record regardless of how many times that prospect has applied.
A failed sync surfaces before a record goes stale
A silent sync failure would have let a wrong status sit unnoticed until someone caught it downstream. Because the integration halts on failure, logs to CloudWatch, and alerts the development team by email and Slack the moment something breaks, a bad sync gets fixed before it reaches a counselor's view of an applicant.
A HubSpot workflow watches the Contact property program_status_stage and fires a webhook, subscription type contact.propertyChange, the moment its value changes to Applicant. That single property change is the only event that starts the HubSpot-to-SIS push.
The integration matches records on sis_id, adenroll_id, and program_sis_id or pv_sis_id rather than email, carried on both the HubSpot Contact and a custom Program Status object. Keying on identifiers the SIS itself issues is what lets one contact hold more than one enrollment without the sync merging or misrouting either one.
The SIS-to-HubSpot direction runs on an hourly poll rather than a webhook, since Campus Nexus Student had no event mechanism to subscribe to. Each run pulls only the records modified in the past hour, keeping the poll load bounded instead of rereading the entire enrollment table.
When either side of the sync fails, the integration halts rather than writing a partial update, and logs the failure to AWS CloudWatch. An email and an internal Slack alert notify the development team immediately, so a broken sync gets attention before it accumulates a backlog.
HubSpot AWS integration SIS Alerts HubSpot Contact + Program Status object AWS Lambda + API Gateway custom integration layer Campus Nexus Student Student Information System CloudWatch failure log Slack alert dev team notification stage changes to Applicant mechanism 1 push via SIS ID mechanism 1 hourly poll past hour mechanism 1 writes update back mechanism 1 logs on failure mechanism 1 alerts dev team mechanism 1
FAQ
Because one prospective student can generate more than one SIS enrollment record, email alone would merge records that should stay separate or miss ones that should match. The integration keys on identifiers the SIS itself issues, sis_id and adenroll_id, carried on both the HubSpot Contact and a custom Program Status object, so each enrollment resolves correctly even when a contact has applied more than once.
The sync halts rather than writing a partial or guessed update, so a failure never leaves a record in an inconsistent state. It logs the failure to AWS CloudWatch and sends an email plus an internal Slack alert, so someone is notified and can act before the failure becomes a backlog of unsynced applicant records.
Continue reading