A change happens.
A record updates — in HubSpot, or in your destination system. A new contact, a deal stage moving, a status changing on the other side. Everyday activity that would otherwise sit in a silo. Either side can be the trigger.
contact.creation / deal.propertyChange-style webhooks. The destination system fires its own equivalent (or we poll on a schedule when no webhook exists). Both directions are wired in the same integration-spec.json.The change reaches us.
The change reaches our infrastructure within a second — whichever direction it’s going. There’s nothing to install on your side; both systems send events to us automatically over the wire.
ap-southeast-2 on Node.js 22 (ARM64).Identity verified.
We confirm the message really came from the system that claims to have sent it — not from someone pretending. Every event is cryptographically checked before we process a single field, regardless of direction.
/{client-slug}/...-client-secret. HubSpot uses X-HubSpot-Signature-V3; the destination system uses its own signing scheme. Failed verification returns 401 immediately on either side.Fields translated.
The data is reshaped to match the destination system’s vocabulary. A “deal stage” in HubSpot might become a different concept entirely on the other side — we map every field deliberately, never by guesswork.
applyTransforms() runs the mapping defined in integration-spec.json. Closed set of formulas: null, enumMap, stripSpaces, lowercase, concat. Anything beyond that is hand-coded business logic, reviewed by a second agent.No duplicates.
We check whether this record already exists on the receiving side — if it does, we update it. If not, we create it. Re-deliveries never produce a duplicate, and changes that we just wrote ourselves don’t loop back as new events.
PK=MAP#{sourceId}) returns the partner ID or null. Echo prevention: every write stamps a hash + timestamp into the mapping; if the next inbound event matches a hash we just emitted, we drop it. Loops can’t form. ConditionExpression on create rejects concurrent writes from both sides racing.Lands on the other side.
The record lands on whichever side wasn’t the source — HubSpot if it started in your destination system, your destination system if it started in HubSpot. Within seconds of the original change. No batch jobs, no overnight syncs, no spreadsheets in between.
withRateLimit() + exponential backoff retry. Mapping persisted to DynamoDB on success along with the echo-prevention hash. On unrecoverable failure: SQS DLQ catches the event and a CloudWatch alarm fires.Monitored, live.
Our Notification Centre sees a heartbeat from this run. If heartbeats stop arriving, your Slack channel hears about it before you do — usually before any of your team noticed anything was wrong.
IntegrationLogger.heartbeat() POSTs to the centre. Down detection at 2× heartbeat interval. CloudWatch alarms on error spikes. Per-record errors accumulate without blocking siblings — one bad record doesn’t halt the flow.Every integration. Observable from day one.
Live heartbeats from each handler, error counts, last-event timestamps — all in one dashboard, all alerting to your Slack.