Skip to main content

Voice Guard: the Federal Dialing-Window Decision Check

Outbound voice dispatch in Devotel Orbit passes one recipient-level decision check before the call is placed: is this recipient reachable under the US federal TCPA window — 8 AM to 9 PM recipient-local time — at this moment? This page documents that check: the gates it runs at, how the verdict is computed, the error surface a blocked dial returns, and what distinguishes this guard from your tenant-owned quiet-hours settings. The worked captures at the end walk each reason through the correction path.

1. Where the voice firewall is deployed

The guard is wired at the dispatch decision point, not the campaign definition. Every outbound voice dispatch channel consults it before placing the call:
  • The public voice-dispatch API — every POST /voice/calls-class dispatch request.
  • Dialer pacing workers — predictive, progressive, preview, and agentless campaign dials.
  • Callback dispatch workers — virtual-hold and scheduled callbacks.
  • The voice gateway pre-dial gate — the final hop before the carrier egress.
All four consult the same shared decision module, so a verdict is identical at every gate — there is no path that skips the check. Every evaluation, allowed or blocked, emits an observability event, so monitoring the gate’s wire-up is a count question, not a code question. Blocked evaluations log with the recipient number masked.

2. How the gate evaluates

The decision is a short, deterministic chain. Non-+1 recipients and non-NANP numbers skip the federal window entirely; everything else is evaluated against recipient-local time.
  1. Recipient check — the guard reads the E.164 destination. A non-US, non-NANP recipient is allowed immediately (reason non_us_recipient); tenant quiet-hours or state overlays may still apply downstream, but the federal wire is skipped.
  2. Timezone resolution — for a +1 recipient the guard resolves the timezone from the NANP area-code map. If no map entry resolves, the check fails closed: because TCPA jurisdiction cannot be disproved, the dial is denied rather than guessed (reason timezone_unresolved).
  3. Local-hour evaluation — the resolved timezone converts the evaluation instant into a recipient-local hour. An unresolvable hour fails closed the same way.
  4. Window comparison — 8 AM to 9 PM recipient-local is allowed (reason inside_federal_window); anything else is denied, and the result carries the UTC instant the window next opens (next_allowed_at), computed with DST-safe hour arithmetic.
The guard supports three evaluation modes at the integration points that ask for a decision without enforcing it: enforce (the default — a block denies the dial), warn (returns the verdict and logs it, for dry-run pacing), and preview (returns the verdict silently, for scheduling previews).

3. What a hard block means

A blocked evaluation in enforce mode returns HTTP 422 with the error code TCPA_FEDERAL_DIALING_WINDOW_BLOCKED. That code is intentionally distinct from TCPA_DIALING_WINDOW_BLOCKED (your tenant quiet-hours gate), so audit filtering can separate federal-window denials from org-configured window denials. The error body carries a structured detail object so your retry logic never has to parse message text: The reason enum:

4. How this differs from your tenant-owned controls

Every other compliance gate — tenant quiet-hours, DNC/consent, country blocks, the org unknown_timezone_policy (skip / enforce_utc / deny) — is something you own: it is configurable per tenant, defaults open, and fails open when resolution is impossible. This guard is the one exception. It accepts no tenant toggle and no per-tenant opt-out, and its timezone-unresolved path always fails closed regardless of your org policy setting. The federal $500–$1,500-per-call penalty (47 U.S.C. § 227(b)(1)(B)) is the reason that asymmetry exists: the guard checks one input (the recipient’s NANP timezone) and owns one window (8 AM–9 PM), independent of anything you configure. Any override posture — for example a tenant state-list exception — belongs in the tenant quiet-hours layer, never here; this gate has no exemption knobs. State overlays still matter, but they intersect downstream: the state calling windows overlay can only narrow within the federal window (e.g. Florida’s 8 PM close); it never reopens a federally closed hour. The relationship is spelled out on quiet-hours preview.

5. Worked captures

The three captures below cover the error body one reason at a time: the blocked-window capture as it lands across a DST switch, the fail-closed capture on a recipient the map cannot resolve, and the same field set on an allowed verdict so you can read details.reason even when nothing was blocked.

outside_federal_window across a DST switch

A campaign dial targets +1 305 555 0101 — America/New_York per the area-code map — in the week of the November 1, 2026 switch back to standard time. The dispatch is evaluated at 22:40 recipient-local on Friday, October 30 (still on daylight time), so local hour 22 is outside 8–21 and the enforce-mode dispatch receives:
This is the DST-safe guarantee in practice. New York switches back to standard time on November 1, and next_allowed_at accounts for it: Saturday, October 31 at 8:00 AM daylight time is UTC 12:00, but had the evaluation landed after the switch — the same 8:00 AM opening read as standard time — the opening would be UTC 13:00 instead. The field already carries whichever offset applies at the opening instant, so schedule against next_allowed_at and never re-derive the opening hour in your own scheduler with a fixed UTC offset.

timezone_unresolved fail-closed

A campaign list carries a Mexican recipient, +52 55 5555 0101. The guard only reads NANP area codes, so a non-+1 recipient that reaches the map by mistake — for example a contact record whose country code was mis-set to a Mexican E.164 — resolves to no timezone entry, and the check fails closed:
Read the shape deliberately: timezone, local_hour, and next_allowed_at are all null, because there is no resolved zone to evaluate and no next opening to compute. The correction is to fix the contact record’s timezone — or remove the non-NANP recipient from the list — then re-dispatch; without that correction every retry fails closed identically. The same fail-closed posture applies to genuine +1 recipients the map cannot resolve (Canadian and Caribbean +1 area codes) — setting the contact’s timezone resolves those.

transactional_lane on an allowed verdict

The guard also surfaces verdicts that allow the call, and the structured fields come along — a warn- or preview-mode evaluation reads:
Read details.reason before planning, even when the call was not blocked: transactional_lane says the call’s surfacing (a live agent dialing one number, a returned call, an account or delivery notification) placed it outside the federal window’s statutory scope, so no next_allowed_at is computed. That is still the one check result the platform will stand behind: an absent or unknown lane evaluates as marketing, so an allow like this one never comes from a lane you neglected to set — it comes from the attribution the platform derived. Tenant quiet-hours and DNC/consent gates still evaluate downstream; the allow verdict only clears this gate.

The correction path

Read reason first, then act on it:
  • outside_federal_window — schedule the dial to next_allowed_at. Queue workers re-poll on that instant; a campaign launch holds to the smallest next_allowed_at across recipients.
  • timezone_unresolved — the recipient resolved to no timezone entry (a non-NANP E.164, or an unmapped Canadian/Caribbean +1 area code). Fix the contact record’s timezone, then re-dispatch; without the correction every retry fails closed identically.
  • non_us_recipient / inside_federal_window / transactional_lane — no action; the verdict is advisory for downstream gates only.

See also