Production / CRM integration platform
Leadplus
A canonical real-estate data platform that reconciles listings, agents, and contacts from seven CRM providers across Australia and the UK.
- Relationship
- Client assignment · SoftwareSeni / Reapit
- Role
- Backend Engineer
- Period
- Oct 2022 - May 2025
At a glance
- Reconciled 7 competing CRM providers into one canonical model serving 5 downstream products
- One broken provider used to silently take down the whole platform — no error, no alert
- Traced and fixed a silent webhook failure that had been dropping client data undetected
- Fixed a concurrency bug corrupting listing images under simultaneous job processing
- Moved file uploads off app servers directly to Cloudflare R2, removing a scaling bottleneck
Stack: PHP, Laravel, GraphQL, MongoDB, MySQL, Redis, Laravel Horizon, React, AWS S3, AWS Lambda, Cloudflare R2, Datadog
Inputs
Outputs
What I built
Led reliability and modernization work across the Leadplus microservices, covering adapter controls, webhook recovery, identity resolution, REAXML, direct-to-R2 uploads, and compatibility for the Laravel and PHP upgrades.
System context
Leadplus reconciles listings, agents, and contacts from seven CRM providers across Australia and the UK into one canonical record model, then serves that model to five downstream products: a listings data service, a website content service, a proposal-generation product, an agency portal and a public property feed API.
The providers are Agentbox, Reapit, Rex, VaultRE, Console, PropertyHUB, and RateMyAgent. Each one differs in field shape, identity, rate limit, and webhook behavior. Some have no stable identifier, some do not send a webhook at all, and some fail without returning an error.
Reapit is both one of those providers and, since acquiring Agentpoint, the owner of the platform itself.
Downstream products cannot absorb that variation. They need one model with a predictable shape, and they need to keep working when a provider does not. The work below sits in that gap.
SoftwareSeni assigned me to Leadplus from October 2022 to May 2025. My role was Backend Engineer across the Accounts, Attachment, Events, and Realty services. The work covered Laravel, MongoDB, GraphQL, adapters, queues, and React when a change crossed the API boundary.
My scope
I led much of the planning across the areas below and stayed involved through implementation, review, and release. The work crossed several microservices and engineers, so delivery depended on technical direction as well as hands-on changes.
I owned
- Reliability across provider adapters: throttling, backoff, health checks, self-healing, and test coverage.
- Planning and compatibility work for the Laravel and PHP upgrades across the services I touched.
- Agent deduplication and identity resolution across providers.
- The REAXML import pipeline.
- Planning and delivery for the direct-to-R2 attachment flow.
- Removal of an unused geocode path, including post-release verification.
I contributed to
- Shared normalization, caching, conditional requests, and client-library changes.
- The RateMyAgent identifier integration.
- React work on the consuming products where a change spanned the API and the interface.
Team-owned
The platform predates me. Core architecture, product direction, and release policy remained team decisions. Changes in my assigned areas were reviewed and released with the wider team.
Key engineering work
Making one broken provider stop breaking the platform
Fault isolation
Problem
A failure at one provider did not stay contained to that provider. Rate-limit rejections arrived as a large volume of errors, a persistently broken upstream kept being retried at full rate, and a failed account lookup could delete an adapter outright. Long syncs ran in the request path and blocked it.
Action
I worked through the adapter layer so each provider owns its own failure behavior: rate-limit throttling shaped by the provider's published limits, backoff when an adapter or webhook job hits repeated errors, and health checks covering adapter status, provider capability availability, and webhook subscription integrity. Adapter processing moved onto the Redis-backed queue behind Horizon, so long syncs stopped running in the request path. I also stopped disabled adapters running at all, and stopped an unrelated lookup failure from deleting one.
Result
Provider failures became isolated and observable, not platform-wide and silent. Over the days after the throttling and backoff rollout I watched Datadog, and error volume dropped and stayed with the failing provider instead of spreading across the platform. That is an aggregate pattern across many requests. I cannot point to one incident and say it was contained. The health checks repair some conditions without a developer, and I wrote the tests asserting that they repair an invalid webhook signature, skip when webhook capability is false, and log failed attempts.
Webhooks that were never validated, and never complained
Production debugging
Problem
Clients reported that Agentbox listings were not reaching the platform and that automated responses fired late or not at all. No error was raised and no job failed; the webhooks were not being processed at all.
Action
I traced individual accounts through the GraphQL playground and found adapters registered with an empty webhook signature. With no signature the payload could not be validated, so it was dropped without a trace. Refreshing it fixed each affected client, but only the symptom. The signature was retrieved only during webhook registration, so a failed fetch left the completed registration holding a blank value with no later recovery attempt. I proposed a self-healing check during normal adapter runs: when the locally stored signature is empty, retrieve it again so the adapter can recover on a later synchronization. Separately, when the provider deprecated its old signature scheme, I implemented validation against the new method.
Result
The silent failure became diagnosable: I traced affected accounts to empty webhook signatures and restored processing by refreshing them. I also implemented the provider's new signature-validation method and proposed retrying signature retrieval during later adapter runs whenever the stored value is empty, allowing that state to recover instead of remaining silently broken.
Two jobs processing the same listing's images
Concurrency
Problem
Listing images were intermittently wrong or missing. Two jobs could process images for the same listing concurrently: both would resolve the listing with firstOrCreate, and both would write.
Action
I led the concurrency fix across the image-processing path. The design combined an atomic application lock, verification that expected images still existed, retry behavior when they did not, and pessimistic row locks around listing and image queries.
Result
Duplicate concurrent processing of the same listing stopped. I added logging inside the job-overlap middleware recording lock acquisition against contention, then read those logs after release to confirm the race had actually stopped rather than merely become rarer.
Attachment bytes travelled through the application servers
Architecture
Problem
Attachments were proxied through application servers, which made file transfer the application's problem: request duration, memory use, and failure handling all scaled with file size.
Action
I led much of the planning and implemented the application-side flow around an existing Cloudflare Worker. The Attachment service authorizes an upload, the Worker returns a signed R2 URL, and the client uploads directly while our services retain metadata. I wired the path into GraphQL, added upload verification and chunked cleanup of expired URLs, and added deployment jobs for development, staging, Australian production, and UK production.
Result
Attachment payloads no longer travel through application servers. Authorization, verification, and cleanup remain inside the platform while R2 handles the file transfer.
Other contributions
Agent identity
Unified every adapter onto one agent-matching function, removed email from the existing-agent check after it proved non-unique, and implemented composite keys so one record can carry several provider identities.
REAXML import and export
The industry XML exchange format, end to end: reading and validating files from S3, creating listings and agents, archiving processed files, and export with a parallel queue for large jobs.
Removing unused traffic
I helped plan the geocode cleanup and removed an obsolete auGNAF-specific path. I verified that address resolution still worked and checked production logs after release.
Support self-service
Fixed the admin interface so non-developers could troubleshoot data issues, and gave users the ability to force a listing sync themselves instead of raising a ticket for an engineer.
Legacy modernization
Helped plan the Laravel and PHP upgrades, then implemented compatibility fixes across the services I worked in. I also cleared PHPStan and PHPUnit issues and kept unit tests off live provider APIs.
Release work
Cut production releases and monitored Datadog after deploys involving my changes.
Cross-team support
Explained root causes and their consequences to engineers on the consuming products, including why a proposed change to the Console sync would reach Realty+ and where a formatted-price requirement conflicted with earlier feedback. Helped the Accounts+ team trace an integration defect where agents were not being associated with their account code, and joined the investigation into a login failure on the shared playground environment. Took part in the Realty+ knowledge sharing sessions.
Outside the roadmap, I built part of a Single Contact View app for an internal hackathon on Laravel, Livewire and Tailwind, pulling two provider APIs behind one cached service interface.
Technical decisions
MongoDB fit the source records because provider payloads carry optional fields and nested structures that vary per provider and change without notice. A relational schema at that boundary would have meant a migration every time a provider adjusted a response.
GraphQL gave five downstream products one interface over the shared model, and a generated client library kept them in step. That library then has to be deployed and supported like any other service, and I spent time on cache-key and versioning defects inside it.
Workers and R2 suited the attachment problem specifically, because upload authorization is small and stateless enough to run at the edge and the file bytes never need to reach our servers. The same reasoning does not carry to the rest of the platform, which needs the application context.
Engineering takeaway
Most of the failures worth finding here never raised an error. An empty webhook signature, a lookup nobody read, and two jobs writing the same listing all looked healthy from the outside, and each one had to be traced from a client-reported symptom back through the adapter. I now assume an external boundary will fail without announcing it, which is why the health checks log their failed attempts instead of only retrying. After the image-concurrency release I read the lock-contention logs rather than assuming the race had stopped, and I have kept that step in reliability work since then.
The product is at leadplus.com.au and the application is login-gated; source, tenant data and dashboards are Reapit's.