Skip to main content

Deactivation-Scrub List Hygiene Runbook: Integrate the Carrier Feed Into Your Churn Lifecycle

A list decays the day it is exported. Subscribers cancel lines, let ports lapse, and carriers reclaim and reassign numbers — so by campaign day, a fraction of any audience points at strangers or at dead lines. The deactivation scrub gives you the carrier disconnect feed as an API; this runbook assembles it into a lifecycle you run per contact: release a number → scrub its new state → reverify before the next send → repeat, instead of treating scrubbing as a one-time pre-flight. Everything here is a tenant-owned control you configure and own: Orbit provides the feed, the API, and the send-path guards; whether you opt in, what you do with a deactivated verdict, and how you pace reverification are your organization’s choices. This page is not legal advice — confirm your obligations with qualified counsel. All endpoints below are rooted at https://api.orbit.devotel.io/api/v1/compliance/deactivations.

1. What the deactivation scrub does

The scrub answers one operational question: did the subscriber’s carrier deactivate this number since you last had a valid relationship with them? Numbering carriers publish a daily feed of deactivated or disconnected numbers; Orbit ingests it centrally and exposes it as:
  • GET /deactivations/check — one E.164 number, verdict + should_scrub boolean.
  • POST /deactivations/scrub — up to 1,000 numbers per request, the pre-campaign batch leg.
  • PUT /deactivations/settings — the per-organization opt-in (deactivation_scrub_enabled), owner/admin-only.
Every response carries feed_synced: while no carrier snapshot is loaded, every verdict degrades to no_data and should_scrub: false — read it before you trust a clear verdict. The full field matrix, the send-path guards (MESSAGING_NUMBER_DEACTIVATED on SMS/MMS, VERIFY_NUMBER_DEACTIVATED on OTP), and the fail-open caveat are covered on the reference page; do not duplicate that reading here — this runbook is the operating layer.

2. Integrate it into the churn lifecycle

Scrub once at import and you ship a stale list next quarter. Wire the three gates so lifecycle state changes on a contact always go through the feed:
  1. Number-release sweep. When your own inventory releases a sender or a destination record ages past its revalidation window (for example a contact not messaged in 90 days), queue the number for a deactivations/check before its next inclusion in any audience.
  2. Scrub on campaign assembly. Before a campaign audience is frozen, run POST /deactivations/scrub in 1,000-number pages with a last_seen equal to the date you last validated each relationship (typically the most recent consent or successful two-way interaction). Drop every should_scrub: true row.
  3. Reverify per contact. A number that came back deactivated is not lost forever — it needs a new relationship event: the subscriber re-joins, confirms the number on a fresh channel, or you record new prior-express consent dated after the carrier’s last_deactivation_date. Re-run the check with that newer last_seen; only then does the contact re-enter lists.
The lifecycle is per contact, not per campaign: store the last check verdict and the last_seen you used on the contact record, so the next sweep only re-checks contacts whose state aged.

3. Pre-send gate syntax and what happens on recycled numbers

The manual check’s only real decision is the last_seen filter:
  • Omit last_seen — any deactivation record on file scrubs the number. Use this for cold lists and imports, where you have no validated relationship date to defend.
  • Supply last_seen — deactivations strictly before that date are ignored; only a newer disconnect scrubs the number. Use this on warm lists, with the date per contact from step 3 of the lifecycle.
Pre-answer the recycled-number question in your runbook, because it will happen: the scrub finding a number on the feed almost always means the carrier has already reassigned it or is about to. The send-path guard then refuses SMS/MMS with MESSAGING_NUMBER_DEACTIVATED (422) and OTP with VERIFY_NUMBER_DEACTIVATED (422) — that is the gate working, not a provider error. The resolution is the reverify leg above: drop the destination, re-establish a relationship, and let a dated consent postdate the last_deactivation_date. Retrying the same send against the same verdict never clears the block.

4. Avoid suppression-ledger side effects

A deactivation verdict is not an opt-out. Writing deactivated contacts into the suppression ledger as if they were STOP replies mixes two different ledgers with two different recovery paths:
  • The suppression ledger encodes a person’s instruction — STOP, unsubscribe, complaint — and is a hard send-gate with its own audit semantics and reversal rules.
  • The deactivation feed encodes a carrier’s network event. Its recovery is reverification + a newer consent date, not an unsuppress.
Keep them separate: route should_scrub: true rows to your contact-status field (for example carrier_deactivated with the last_deactivation_date), and let suppression stay reserved for genuine opt-outs. That separation also keeps a bulk import of “recycled numbers” from polluting the ledger your opt-out audit reads — with false positives an auditor later has to explain away. If a deactivated subscriber also opts out on the new relationship, that STOP enters suppression through the normal keyword path — one ledger per truth.

5. Verify the integration

Run this checklist before you rely on the gate. None of it sends a message, so none of it consumes campaign coverage:
  1. Settings round-trip. GET /deactivations/settings returns your opt-in and a live feed_synced; on the SaaS platform the feed is synced centrally — if feed_synced is false, stop and treat every verdict as no_data until it flips.
  2. Known-unknown probe. Check a number you have no history with, without last_seen. Expect a well-formed verdict (deactivated, active, or no_data) and a should_scrub that equals status === "deactivated" — never an error.
  3. Batch page-through. Scrub a slice of a real audience, confirm checked equals your page size and scrub_count + kept_count reconciles. Verify your drop list is exactly the should_scrub: true rows.
  4. Send-path proof with one live send. Send a single SMS to a contact you know is deactivated (from your own sweep). Expect a 422 with MESSAGING_NUMBER_DEACTIVATED and a written audit entry. That single deliberate refusal proves the guard fires — do not instrument this with bulk traffic.
  5. Lifecycle reload. Record a fresh consent dated after the last_deactivation_date, re-check with the new last_seen, and confirm the verdict flips to active.
The guards are fail-open by design, so a lookup error never black-holes a send; that also means verification is how you know the gate is actually armed, not just absent.

6. Failure-mode codes

For the full row set see Error Codes and Messaging pre-send gates.
See also: