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 HubSpot connector and no API documentation the university had been given, 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, offered no HubSpot connector, and the university had no API documentation to build against. 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. Building against an SIS with no published API means reverse engineering a live vendor's private data model. It needs someone on the Admissions or IT side to monitor the CloudWatch alerts and own the fixes when the vendor changes something upstream. It isn't 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. 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 hadn't caught up. A single contact could also hold more than one SIS enrollment, so matching on email alone produced false matches.
As admissions volume grew, the two systems drifted further apart between manual checks. A stale status left a counselor working from the wrong picture of where an applicant stood.
Our Approach
We first looked for a native connector between HubSpot and Campus Nexus Student. 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, with CloudWatch as the logging layer.
The sync triggers when a Contact's program_status_stage changes to Applicant. That fires a webhook that pushes the record to the SIS in near real time. We didn't match on email, because one contact could hold more than one SIS enrollment and email alone would match them wrongly. Instead 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, pulling only SIS records modified in the past hour. We traded real-time sync for a build that didn't depend on the SIS exposing anything it wasn't 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 hourly poll pulls every SIS record changed in the past hour, 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.
Staff used to update HubSpot and the SIS by hand, because Anthology never published an API or a connector. We built middleware on AWS Lambda and API Gateway instead. Now a change of "program_status_stage" to Applicant fires a webhook that pushes the record to the SIS, matched on "sis_id" and "adenroll_id" instead of email, because one contact can hold more than one enrollment. An hourly poll brings updates back.
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