Sports, Fitness & Athletics// diagnostic

Lapsed members are still getting through the door

In short

A turnstile that admits a cancelled member is rarely reading a wrong membership record — it is reading its own cached copy of one. Access controllers hold a local allow-list so the door keeps working when the network drops, which makes revocation something you have to push, and puts the fault in the timing and direction of that push rather than in the membership system.

Key takeaways

  • The door decides locally. Membership status only reaches it when something pushes a change to the controller.
  • A sync that adds and updates but never deletes will never revoke anybody, however often it runs.
  • Diff the controller's credential list against live membership at one instant — that single check names the cause.
  • Most of the exposure is usually billing latency, not door latency: dunning can hold a lapsed member active for days.
  • If you accept a cache window, write the number down and pair it with a daily lapsed-entry report at the desk.

The controller on the wall by the turnstile holds its own list of credentials that may open the door. It has to: the door is expected to keep working when the internet drops, the card reader does not have time to ask a cloud service on every swipe, and a barrier that fails shut during a school swimming lesson is its own incident. So the membership record is not what admits anyone. A cached copy of it is.

That reframes the problem entirely. You are not looking for a member record that says the wrong thing — you are looking for a revocation that never left, left late, or arrived and was ignored. Attendance capture is a separate job with separate constraints, handled in check-in that assumes there is no network; this page is only about who the barrier lets through.

The check: diff the door against live membership at one instant

Everything below is guessing until you have this diff. It takes an afternoon and it identifies the cause on its own, because each cause leaves a different shape in the output.

  1. Export the controller's credential list. Every access system can produce this, even the ones with no integration surface — a management client export or a printed report is enough. Note the export time to the minute.
  2. Snapshot live membership at the same minute. Member id, credential id, status, status-effective date, and the timestamp of the last status change.
  3. Join on credential id, not name. Names collide, are re-keyed and get married. If the 2 systems share no credential identifier, that is your finding and the diff stops here.
  4. List credentials present on the door with a membership that is not entitled to enter. For each one, compute hours between the status change and the export.
  5. List the reverse — entitled members with no credential on the door. These generate desk queues and are the same defect in the other direction.
  6. Pull the entry log for the past 30 days and mark every entry that happened after its member's status change. Count them, and count the distinct members. That number is what makes the case for changing anything.

Seven causes, and what separates them

CauseShape in the diffConfirming evidenceOwner of the fix
Scheduled push onlyStale credentials, none older than 1 sync intervalIntegration log writes only at the sync timeThe access integration
Sync never deletesCredentials revoked months ago still presentThe door list is longer than the entitled listThe access integration
Reader trusts the card formatCards that were never enrolled open the barrierPresent an unenrolled card of the right formatThe access system installer
Freeze maps to no door ruleFrozen members entering, status correct everywhereNo freeze row in the status-to-access mappingWhoever owns the status vocabulary
Status has not flipped yetDiff is clean; the member is genuinely still activeDunning retries still running against the cardBilling policy, named person
Credential shared or clonedEntries with no plausible member behind themTwo entries at 2 sites minutes apartFront desk policy
Controller fails open offlineDiff is clean and entries happen anywayEntry times cluster in known network outagesThe access system installer
Read across from the shape of your diff

Where the exposure actually accumulates

Clubs assume the door is slow. Usually the door is the smallest term. Take a member whose card declines on a Tuesday, on a dunning schedule that retries on day 3, day 7 and day 14 before the membership is marked lapsed, with an access sync that pushes a full list at 03:00.

StageClock startsDelayOwner
Payment failsFirst declined chargeSame dayPayment provider
Dunning completesFirst decline14 days by policyWhoever set the retry schedule
Membership status flipsFinal retry failsImmediate, or the next batch jobThe membership platform
Access list updatedStatus changeUp to 24 hours on a nightly pushThe access integration
Controller applies itPush receivedSeconds — or never, if the sync mergesThe controller configuration
Exposure decomposed for one failed payment

Fourteen days of that fifteen belong to billing policy. Shortening the door's window from 24 hours to 30 seconds removes 1 day of a 15-day exposure and feels like fixing all of it, which is exactly why the diff has to come before the engineering. The same misattribution shows up across the whole silo — full class, empty floor is the same timing defect wearing member-behaviour clothes.

Making revocation a first-class event

Most membership platforms emit an event when somebody joins and treat the end of a membership as a state that quietly becomes true. That asymmetry is the root of every cause in the table above, and it is fixable in the model rather than the integration.

  • Emit a revocation event, not an absence. It carries member id, credential id, the reason, the effective instant and an idempotency key, so replaying it twice is a no-op.
  • Map every membership state to a door decision explicitly. Active, in arrears, frozen, expired, cancelled, banned, guest, trial. A state with no row in that table will be handled by whatever the default is, and the default is usually admit.
  • Make freeze a door state of its own. A frozen member should be admitted or refused deliberately, and clubs genuinely differ — what breaks is having the policy in a handbook and not in the mapping table.
  • Acknowledge from the controller, not from the queue. A push accepted by the head-end and never delivered to the panel is the failure that survives every log you look at.
  • Reconcile daily regardless. Re-read the controller list, diff it, and alert on any credential the door holds and the membership system does not — the same degrade-and-reconcile discipline that keeps operations systems useful when connectivity drops.
  • Extend the same mapping to entitlements off the premises. A member who can no longer enter should not still be watching the members' stream, which is the identical cached-entitlement decision examined in a public video platform or your own club app.

Systems tell each other who exists. Almost none of them tell each other who has stopped existing, and that silence is where every stale allow-list comes from.

Multiple sites multiply the window, they do not share it

A club with 3 venues typically has 3 controllers, and often 3 different generations of hardware bought at 3 different times. Each has its own sync, its own interval and its own failure mode, so the exposure is the worst of the 3 rather than the average. Members and coaches move between sites the same evening — the movement modelled in one term timetable across three venues — so a credential revoked correctly at the main site can still open the barrier at the annexe.

Keep 1 canonical entitlement service and treat each controller as a downstream replica with its own health check. A per-site dashboard showing last successful sync, credential count on the door and credential count expected takes an hour to build and turns an invisible failure into a visible one.

Decision tree, with an owner on every branch

  1. Credentials removed months ago are still on the door. The sync merges. Move to a replace-or-explicit-delete write before you change anything else, because no scheduling improvement helps while deletions never arrive.
  2. Stale by hours, none older than one interval. This is an honest cache window. Choose deliberately between event-driven revocation and an accepted window, and if you accept it, write the number down.
  3. An unenrolled card opens the barrier. The reader is validating the card format rather than the credential. Nothing in your software can fix this; it is a controller configuration change and it is the most serious line on the list.
  4. The membership record still says active. Nothing is wrong at the door. This is a dunning and status-vocabulary question, and the fix belongs to whoever set the retry schedule.
  5. Frozen members are entering. Add the freeze row to the mapping table and decide the policy explicitly, once, for every site.
  6. Entries with no member behind them. Credentials are shared or cloned. That needs a desk policy and a second factor at the barrier, not an integration change.
  7. Everything reconciles and it still happens. The controller is failing open when the link drops. Ask the installer what the offline behaviour is configured to be, in writing, and decide whether you accept it.

Building the entitlement service, the revocation events and the nightly reconciliation is bounded AI agents and automation work in the sense that matters here — plumbing with alarms on it, not intelligence. The rest of this silo sits under scheduling, attendance and the facility, inside our sports, fitness and athletics practice.

Frequently asked questions

Short answers to the follow-ups this page tends to raise.

Why does gym access control let in cancelled members?

Because the controller decides locally from a cached allow-list rather than asking the membership system on each swipe. A cancellation only reaches the door when something pushes it, so the usual causes are a sync that runs on a schedule, a sync that adds and updates but never deletes, or a membership status that has not flipped yet because dunning is still retrying the payment. Diff the controller's credential list against live membership status at one instant and the shape of the difference tells you which.

Should the door check membership in real time instead of caching?

No — cache, and push revocations promptly. A door that asks a remote service on every swipe stops working when the connection does, which is unacceptable at a barrier with a queue behind it. The right design keeps the local allow-list for the decision and treats revocation as an event pushed within seconds, with a full reconciliation sweep daily to catch anything the event stream lost.

How quickly should a cancelled membership stop opening the door?

Within minutes if revocation is event-driven, and inside a stated window otherwise. The number matters less than it being written down and monitored: an undocumented window has no upper bound, because nobody notices when it grows. Whatever you choose, pair it with a daily report of entries by members who were not entitled, so the exposure stays visible.

Should a frozen membership still open the turnstile?

That is a policy choice, and the failure is leaving it unmade. Some clubs admit frozen members for paid casual entry; others refuse entry entirely so the freeze means something. Either is defensible, but the decision has to exist as a row in the mapping from membership state to door decision — a state with no row falls through to the system default, which almost always admits.

  • access control
  • membership status
  • integration
  • revocation
// shipped work

The work behind this page

Builds from our portfolio that this page draws on.

Working on something in this space?

Tell us where you are in a sentence or two. We'll tell you honestly whether we're the right team, and what a sensible first slice of the work looks like.

Start the conversation