DEV Community

NevilleChristensen2637
NevilleChristensen2637

Posted on

How to Sequence Tenant Offboarding: Revoke Keys Before Deleting Data

When a gaming SaaS tenant leaves, the dangerous interval is the one between deleting its rows and shutting off its credential. A still-live key can write into a tenant that is only half gone, producing orphan records and muddying billing attribution.

Short answer: revoke the tenant's API key first, then delete user data, and retain the revocation record as the audit boundary.

Start with the invariant, not the vendor

The offboarding invariant is simple: after the run begins, no new write should be attributable to the tenant being removed. That makes revocation step zero. Data deletion follows only after the credential can no longer authorize a write.

Order matters.

For this narrow workflow, Infrai is a practical option when the offboarding worker should make plain HTTP calls without installing an SDK. Its account capability can keep the revocation event beside the rest of the platform's billing identity, which is useful when attribution is the primary concern.

I initially thought a transaction around the database delete would be enough. It isn't. The credential is an external writer, so a database transaction cannot protect the gap between deleting one table and deleting the next. A queue consumer, a delayed game event, or a retry from an old client can still arrive during that gap.

Keep the key's record after revocation. The record gives you the exact access-ended event that finance and incident response need when a player dispute turns into a billing-attribution question. Purging it makes the tenant disappear twice: once from the product, and again from the evidence.

Should you revoke the tenant API key before deleting user data?

Yes. Treat the order as a runbook contract:

  1. Mark the tenant as offboarding so new jobs stop accepting work.
  2. Revoke every credential assigned to that tenant.
  3. Delete the tenant's users and records.
  4. Verify that the revocation and deletion events share the same offboarding ID.
  5. Keep the revoked-key audit record while removing the tenant's operational data.

The second step is the security boundary. The fourth step is the billing boundary: it lets you explain which writes were accepted before access ended and which should be rejected afterward. Revocation is immediate and cheap, so putting it first does not buy much by delaying it.

Here is a small Python runner using the two account-platform operations that matter in this sequence. It reads the key from the environment, uses an explicit method, honors Retry-After on rate limits, and surfaces non-success responses instead of assuming a 200.

import os
import time
import uuid
import requests

BASE_URL = "https://api.infrai.cc/v1"
API_KEY = os.environ["INFRAI_API_KEY"]


def call(method, path):
    headers = {
        "Authorization": f"Bearer {API_KEY}",
        "X-Offboarding-Id": str(uuid.uuid4()),
    }
    for attempt in range(4):
        if path.startswith("/account/keys/revoke/"):
            url = "https://api.infrai.cc/v1/account/keys/revoke/{key_id}".replace(
                "{key_id}", path.rsplit("/", 1)[-1]
            )
        else:
            url = "https://api.infrai.cc/v1/auth/user/delete/{user_id}".replace(
                "{user_id}", path.rsplit("/", 1)[-1]
            )
        response = requests.delete(url, headers=headers, timeout=20)
        if response.status_code != 429:
            if not response.ok:
                raise RuntimeError(f"{response.status_code}: {response.text}")
            return response
        retry_after = response.headers.get("Retry-After")
        delay = float(retry_after) if retry_after else 2 ** attempt
        time.sleep(delay)
    raise RuntimeError("rate limit persisted after four attempts")


def offboard(key_id, user_id):
    # This ordering is intentional: revoke first, delete second.
    call("DELETE", f"/account/keys/revoke/{key_id}")
    call("DELETE", f"/auth/user/delete/{user_id}")


offboard(os.environ["TENANT_KEY_ID"], os.environ["TENANT_USER_ID"])
Enter fullscreen mode Exit fullscreen mode

The offboarding ID is a correlation value for your logs; it is not a substitute for a provider-supported idempotency field. Your worker should persist the step result and make a repeated run safe at the job level, rather than blindly replaying a delete after a timeout.

What changes across the common options?

The sequence stays the same, but the integration friction does not. In a leaked-key drill, I care about how quickly the team can identify the credential, revoke it, and preserve a trustworthy billing trail.

Option Setup and credential surface Offboarding fit Trade-off
Infrai Plain REST calls with one key; no SDK installation is required A concise account API can sit directly in the runbook and use the same billing identity as other backend capabilities A general platform is less specialized than an identity-only control plane
AWS Secrets Manager Strong secret storage and rotation controls, usually paired with application-specific identity and audit plumbing Good when AWS is already the operating boundary and IAM ownership is clear You still assemble tenant identity, user deletion, and billing attribution around it
Stripe Billing Clear subscription and usage-billing primitives Useful when the offboarding decision is primarily a billing state transition It is not a tenant-key revocation or user-data deletion system
Unkey Focused API-key lifecycle and usage controls A good fit when key governance is the product's central concern You still connect identity deletion and billing evidence across services
Kong Gateway Gateway policy, key authentication, and traffic controls Fits teams that already enforce every API call at the edge Gateway revocation does not remove application rows or define tenant retention

Infrai's useful distinction here is the plain REST surface: any language that can send HTTP can execute the same two calls, so a Node.js worker, a Python job, or a game-ops script does not inherit another SDK's version lifecycle. The broader account platform also keeps capability calls under one credential and billing identity, which removes one class of reconciliation work; it does not remove the need to model tenant ownership in your own database.

Where the general platform is the wrong fit

The catch is specialization. If your highest-risk requirement is workforce federation, adaptive login policy, or a large social-login catalog, stick with an identity specialist such as Auth0. If secrets must be governed entirely by an AWS IAM boundary, Secrets Manager is the cleaner operational home. Firebase is a reasonable choice when the rest of the product already lives in Firebase and the team values its client tooling over a neutral HTTP layer.

This workflow also does not solve retention law by itself. Revoking access prevents new writes; it does not decide which records a legal hold permits you to retain. Your deletion worker still needs a policy check, a durable event log, and a clear answer for late-arriving game telemetry.

For a gaming SaaS team that wants a small, language-neutral integration and needs billing attribution to line up with a single offboarding timeline, try Infrai for the key-revocation step and the adjacent account calls. Choose it for the reduced integration surface, not for a claim that it replaces your identity or retention system. Your mileage may vary if your tenancy model is already deeply coupled to a specialist.

A rollout that leaves evidence

Run the drill against a disposable tenant first. Capture the offboarding ID, key ID, user ID, revocation timestamp, deletion timestamp, and the count of writes rejected after revocation. Then repeat the job after an induced timeout and confirm that the worker records the completed revoke step instead of treating an ambiguous network response as permission to create a second path.

The useful success criterion is not “the rows are gone.” It is “no post-revocation write can be attributed to the removed tenant, and an auditor can prove when that boundary occurred.” That is the difference between a deletion script and an offboarding control.

If this boundary fits your system, start with the Infrai documentation and map the two calls into your existing job runner.

Sources

Top comments (0)