Recipes← all recipesView on GitHub

Check consent before an outbound call

Voiceoutbound call consent and hours checks

Check stored consent and the callee's local calling window before your code sends an outbound dial request.

complianceoutbound

The claim

The platform’s part is one dial on POST /api/calling/calls. The recipe is the ordering around it. place() looks the number up in your consent store and converts the current time to the callee’s zone with zoneinfo. It checks that time against a window. Each failed consent or window check raises NoConsent with the reason before the code reaches client.calling.dial. A clock with no time zone raises ValueError instead. The verifier records no HTTP request for a refused call, because the code never builds one.

How it works

def allowed(number, now=None):
    if now is not None and now.utcoffset() is None:
        raise ValueError("now must carry a time zone")
    record = CONSENT.get(number)
    if not record:
        return "no consent on record"
    if not record["consented"]:
        return "consent withdrawn"
    local = (now or datetime.now(ZoneInfo("UTC"))).astimezone(ZoneInfo(record["tz"]))
    if not WINDOW[0] <= local.time() <= WINDOW[1]:
        return f"outside the calling window, it is {local:%H:%M} in {record['tz']}"
    return None

def place(number, message, now=None):
    reason = allowed(number, now)
    if reason:
        raise NoConsent(f"not calling {number}: {reason}")
    return client.calling.dial(**{"from": FROM, "to": number, "timeout": 25, "swml": ...})

What the platform receives, after every check passes:

{"command": "dial",
 "params": {"from": "+15550001111", "to": "+15557654321", "timeout": 25,
            "swml": {"version": "1.0.0", "sections": {"main": [
              {"answer": {}}, {"play": {"url": "say:Your bike is ready."}}, {"hangup": {}}]}}}}

The code judges the window in the callee’s zone, which is part of the consent record. It refuses a callee in Los Angeles at 21:30 even when the server clock reads 04:30 tomorrow. It allows one at 19:00 when the server reads 02:00. CONSENT here is a dictionary. With counsel, decide which consent details your production store must retain.

Limitations

This is the ordering, not the law. Which hours count, what consent must say, and how long a record is good for are yours to decide with counsel, and vary by jurisdiction.

The store is in memory. handle-opt-outs-yourself handles the messaging opt-out with a record of its own.

What to change first

Change WINDOW to (time(9, 0), time(22, 0)) and run the verifier. The 21:30 refusal no longer happens and that assertion fails. The window is a value you set, and the verifier pins the one this recipe chose.