> ## Documentation Index
> Fetch the complete documentation index at: https://docs.orbit.devotel.io/llms.txt
> Use this file to discover all available pages before exploring further.

# Troubleshooting: TCPA voice-guard sentinel breadcrumbs

> Verify the federal TCPA voice-window guard actually fired at each dial gate — which send paths are gated, what the compliance.tcpa.federal-window Sentry breadcrumb proves, and how to trace a 422 TCPA_FEDERAL_DIALING_WINDOW_BLOCKED back to the gate that held it.

# Troubleshooting: TCPA voice-guard sentinel breadcrumbs

A blocked campaign send with `422 TCPA_FEDERAL_DIALING_WINDOW_BLOCKED` is
only half the story. The other half is *proving the guard actually ran* on
the path that held the call — that the dial gate in question is wired
through the TCPA window evaluation at all, and that the decision you saw
came from the gate you expect. Every one of Orbit's voice dial gates emits
a Sentry breadcrumb under the category `compliance.tcpa.federal-window`
on **every** evaluation, allowed or blocked. That breadcrumb stream is the
audit trail this page works: if a gate is wired, the breadcrumb count is
non-zero; if the count for a gate sits at zero for weeks, the wiring never
took.

This page supplements [Troubleshooting: resolve dialing-window blocked
voice calls](/troubleshooting/tcpa-window-blocked-calls), which maps each
422 code to its fix. This page works the observability layer: which gate
fired, how to confirm it ran, and how to trace one blocked send through
breadcrumbs until you reach the decision.

## The four dial gates and their preconditions

Every outbound voice send path evaluates the federal window at its own
gate. The gates share the window definition and the fail-closed posture —
they differ in **when they fire**, **who calls them**, and whether a
blocked evaluation **throws, defers, or only logs**.

| # | Gate (send path)                                                      | Precondition to reach the gate                                                                                                                                                                                                                                                                                  | Blocked outcome                                                                                                                                                                                                                                                                        | Breadcrumb emission                                                                                        |
| - | --------------------------------------------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ---------------------------------------------------------------------------------------------------------- |
| 1 | **Ad-hoc dashboard / softphone dial** (API voice compliance guard)    | Every outbound voice call created through the API — unless a dev/staging test call passes the explicit test-call bypass. The calling organization must also clear its own opt-in RMD origination check first when enabled (that's gate 0 on this path, and it reads the org's *own* filing, not the recipient). | **Advisory only** — the window decision is logged, never thrown. The only hard block on this path is your own opt-in voice quiet-hours gate (`TCPA_DIALING_WINDOW_BLOCKED`) when you've enabled it.                                                                                    | Emitted inline at the gate, **before** the evaluation — a thrown block is always preceded by a breadcrumb. |
| 2 | **Campaign dialer pace** (predictive / progressive / preview dialing) | Each contact the pacer considers dispatching, but **only for +1 NANP recipients** — non-+1 numbers never reach the gate (the federal window is US-only). Other jurisdictions gate through your opt-in quiet hours, if enabled.                                                                                  | **Non-throwing defer** — the evaluation returns the next allowed instant and the contact is deferred to `next_allowed_at` (or a conservative one hour when even that can't be computed), never burned.                                                                                 | Per evaluation at the pacer.                                                                               |
| 3 | **Callback dispatch** (virtual-hold scheduled callbacks)              | Each callback the dispatcher is about to originate; same +1 NANP precondition.                                                                                                                                                                                                                                  | **Non-throwing defer** — the callback is rescheduled to `next_allowed_at`, same shape as gate 2.                                                                                                                                                                                       | Per evaluation at dispatch time.                                                                           |
| 4 | **Voice-gateway pre-dial** (the last gate before SIP leaves)          | Every outbound leg the voice gateway originates.                                                                                                                                                                                                                                                                | **Hard block with throw** — `DevotelError(TCPA_FEDERAL_DIALING_WINDOW_BLOCKED)` → HTTP 422 toward the caller, no SIP dispatched. Also **fails closed** on an unresolvable timezone: a +1 recipient whose area code maps to no IANA timezone is refused even inside the nominal window. | Emitted **before** the guard runs, so the blocked call is always preceded by its breadcrumb.               |

Two consequences of the table fall out immediately:

* **Deferral is not failure.** Gates 2 and 3 never throw — they move the
  send to `next_allowed_at`. A "stuck" campaign with zero blocks in the
  API and a full contact list is usually contacts deferred to the next
  open window, not an error.
* **Only gate 4 can produce the 422** `TCPA_FEDERAL_DIALING_WINDOW_BLOCKED`
  on an actual dial attempt. Gates 1–3 don't throw it. So when you see
  that code, you know which firewall fired — what remains is confirming
  the breadcrumb trail behind it (worked example below).

## Breadcrumb signal semantics

Every gate writes through the same category, so one query covers all four:

| Signal               | Value                                                                                                                                                                                                                                                                                     |
| -------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| **Category**         | `compliance.tcpa.federal-window` — the exact Sentry breadcrumb category, uniform across all four gates.                                                                                                                                                                                   |
| **Timing**           | *Before* the evaluation, not after. A thrown block at gate 4 is always preceded by its breadcrumb; an absent breadcrumb is never "the block happened but wasn't seen" — it means the gate wasn't reached.                                                                                 |
| **Coverage**         | Fires on **every** evaluation — allowed and blocked. The count is the wiring proxy: a gate that is genuinely running produces breadcrumbs continuously; a gate whose count sits at 0 for >30 days after wiring is a gate whose wiring didn't take.                                        |
| **Payload fields**   | `recipientPrefix` (first \~6 digits of the recipient, rest masked — match a specific send on this), `mode` (`enforce`, `warn`, or `preview`), `hasTimezoneOverride` (whether the caller passed an explicit recipient timezone). Gate 1 additionally records the tenant schema.            |
| **Message variants** | Gate 1 labels its breadcrumb as the API voice-dial advisory check; the pure-package gates label theirs "TCPA federal window guard invoked" (throw variant at gate 4) or "TCPA federal window check (no-throw)" (gates 2 and 3). Filter across variants by **category**, not message text. |

Example Sentry filters (Discover → breadcrumbs, or the issue's breadcrumb
stream):

* All gates, one category: `breadcrumb.category:compliance.tcpa.federal-window`
* The gate that can throw (gateway pre-dial): narrow the above by environment
  \= voice-gateway service; the breadcrumb immediately preceding a
  `TCPA_FEDERAL_DIALING_WINDOW_BLOCKED` event is the evaluation that threw.
* A specific blocked send: add `recipientPrefix` for the number from the
  error payload and check that exactly one breadcrumb with that prefix was
  written in the seconds before the 422.

Do **not** filter by message text across gates — the message differs per
emission point; the category is the join.

## "Wiring didn't take" triage — confirm the guard actually ran on a block

Work a `422 TCPA_FEDERAL_DIALING_WINDOW_BLOCKED` the way the sentry story
was designed to be worked:

1. **Confirm the error belongs to the throwing gate.** Read the code and
   the send path: campaign launches and gateway-originated legs produce
   `TCPA_FEDERAL_DIALING_WINDOW_BLOCKED`; ad-hoc dashboard dials produce
   only advisory logs. If your blocked send came from the campaign surface
   but the error is `TCPA_DIALING_WINDOW_BLOCKED` (no `FEDERAL`), that's
   your own opt-in quiet-hours gate — a different page
   ([resolve dialing-window blocked calls](/troubleshooting/tcpa-window-blocked-calls))
   maps the fix.
2. **Find the breadcrumb for the block.** Query Sentry for
   `breadcrumb.category:compliance.tcpa.federal-window` in the minutes
   around the 422, then narrow by the `recipientPrefix` you read off the
   error details. The breadcrumb **must** exist: it fires before the guard
   runs, so a thrown block without a preceding breadcrumb means the throw
   didn't come from the instrumented gate — escalate with the error body.
3. **Check the sibling gates.** A campaign send crosses gate 2 (pace/defer)
   before it ever reaches gate 4. If the pace gate's breadcrumb for the
   same recipient shows the contact was deferred to `next_allowed_at`, the
   campaign surface behaved correctly and the 422 came from a send that
   *skipped* the pacer path; compare the received-at timestamps to confirm
   ordering.
4. **The zero-count signal.** An empty result for the category over weeks
   — not hours — on a send path you know is dialing means the gate's wiring
   never took on that path. Cross-check the gate's preconditions first
   (send path 1 or 4? recipient +1?), then report the path with its tenant
   context. A silent gate on a path that should dial is a platform defect;
   the page exists precisely so the drift surfaces in the breadcrumb count
   rather than in an incident.
5. **Sanity the fail-closed fallback.** A block whose `reason` is
   `timezone_unresolved` still produces its breadcrumb — fail-closed means
   the guard **ran** and couldn't prove the window, not that it was
   skipped. An unresolved-timezone block with no breadcrumb is the same
   kind of defect as step 2.

## Posture note — the one carve-out

Per [your compliance boundary](/compliance/consent-default-policy), every
compliance control on the platform is **yours** — default-open, opt-in,
fail-open on unresolvable input. The federal TCPA voice window is the
**single carve-out** to that rule: platform-global, fail-closed, and with
**no tenant toggle**. The statutory penalty (47 U.S.C. § 227(b)(1)(B),
$500–$1,500 per call) is not yours to waive, so the control is not yours to
disable. It accepts no `skip` flag on a real-customer path, no quiet-hours
knob that widens the federal window, and no posture change from any
settings page. The tenant opt-in voice gate layers **on top** — you can
only be stricter than the federal floor. Treat the sentinel breadcrumbs as
the observability contract that keeps this posture verifiable: the guard
can't be turned off, and the breadcrumb stream is how you confirm it never
silently stopped running. Don't try to weaken either property; that would
reopen the exact class of incident the carve-out closed. (The dev/staging
test-call bypass on gate 1 exists only for operator test numbers explicitly
marked as such — it never applies to a customer dial.)

## Worked reproduction: build-call → block → breadcrumb → resolution

A campaign launches at 7:40 AM Eastern toward a Florida recipient whose
area code resolves `America/New_York`. Walk the whole chain:

1. **Build-call.** The campaign pacer (gate 2) reaches the contact. The
   recipient is +1 NANP, so the gate evaluates. Recipient-local time:
   7:40 AM — inside neither the federal 8–9 AM floor nor Florida's overlay.
   The gate **defers to `next_allowed_at`** and writes a breadcrumb:
   category `compliance.tcpa.federal-window`, `recipientPrefix` matching
   the number, `mode: preview` (`check` variant — no-throw). The contact
   is held, not burned.
2. **Block.** Your own scheduler bypasses the pacer's deferral and pushes
   the same recipient straight to the gateway. Gate 4 evaluates — the
   breadcrumb fires first (same category, `mode: enforce`), then the guard
   throws `TCPA_FEDERAL_DIALING_WINDOW_BLOCKED` with
   `next_allowed_at` set to the next 8 AM recipient-local. You receive
   HTTP 422; no SIP is dispatched.
3. **Breadcrumb.** Query
   `breadcrumb.category:compliance.tcpa.federal-window` + the number's
   prefix. You find two breadcrumbs: the deferral at gate 2 a few minutes
   earlier, and the thrown evaluation at gate 4 immediately before your
   422\. Both are expected; the presence of the second one is the
   confirmation the throwing gate actually ran on this send. If it were
   missing, you'd be in step 2 of the triage section — that would matter.
4. **Resolution.** Hand the recipient to the pacer's deferral
   (`next_allowed_at` from either signal — the pacer already computed it)
   or re-run the send at the window open. The block doesn't retry-safe;
   a retry inside the same blocked hour reproduces the same 422. If your
   own scheduler pushed the send, fix the scheduler to honor the deferral
   rather than hammer the gateway.

The whole episode, as the breadcrumb trail reads it, is: one defer at the
pacer, one throw at the gateway, zero silent gaps. That is the sentinel
doing exactly what it exists to do.

## See also

* [TCPA federal voice guard](/concepts/tcpa-federal-voice-guard) — the
  concept page for the one gate this sentinel observes: the carve-out,
  fail-closed posture, and why no tenant toggle exists.
* [US state calling windows](/compliance/state-calling-windows) — the
  mini-TCPA overlays the same evaluation composes with before the gate
  decides.
* [Troubleshooting: resolve dialing-window blocked voice
  calls](/troubleshooting/tcpa-window-blocked-calls) — the error-code →
  fix mapping for every code in the TCPA family.
* [Quiet hours configuration](/guides/quiet-hours-configuration) — your
  opt-in gate, `unknown_timezone_policy`, and where to set a stricter
  window.
* [Send gates](/compliance/send-gates) — the full chain of send-time
  controls the federal guard sits inside.
