Some behaviors only show up as time passes: an installment that goes past due, an obligation that comes due, a subsidy that requires a minimum membership period. Testing them by waiting real days isn’t an option. A test clock is a virtual time source: you freeze it on a date (frozen_time) and move it forward as many times as you like; its current_time is the “now” of your scenario.

Sandbox keys only

How they work #

Clocks don’t store state on the server. All the state (the frozen date and the seconds advanced) travels inside the id, in the form tcl-<data>-<signature>:

  • Every advance returns a clock with a NEW id. Always keep the id returned by the latest call and continue from it. The old id is still valid, but it represents the clock before the advance.
  • The id carries an integrity signature: if you edit it by hand (for example, to “rewind” time) the gateway rejects it with 422.
  • Since the result depends only on the id and the advance, the same advance on the same id always returns the same clock. That’s why these routes don’t use Idempotency-Key.
  • They only move forward: delta_seconds must be >= 0 and at most 63 days (5,443,200 seconds) per call. For bigger jumps, advance several times.
  • frozen_time accepts ISO-8601 (2026-10-01T00:00:00Z) or Unix seconds. A date without a time zone is interpreted as UTC.
sequenceDiagram
    participant T as Your test
    participant G as Gateway (sandbox)
    T->>G: create(frozen_time = 2026-10-01T00:00:00Z)
    G-->>T: tcl-A (current_time 2026-10-01, advanced 0)
    T->>G: advance(tcl-A, 30 days)
    G-->>T: tcl-B (current_time 2026-10-31, advanced 2.592.000)
    T->>G: advance(tcl-B, 15 days)
    G-->>T: tcl-C (current_time 2026-11-15, advanced 3.888.000)
    T->>G: get(tcl-B)
    G-->>T: tcl-B is still at 2026-10-31

Create, advance and retrieve #

import os
from compense_gateway import CompenseClient

client = CompenseClient(api_key=os.environ["COMPENSE_API_KEY"], environment="sandbox")

reloj = client.sandbox.test_clocks.create(frozen_time="2026-10-01T00:00:00Z")
print(reloj.id, reloj.current_time)                       # tcl-… 2026-10-01T00:00:00+00:00

reloj = client.sandbox.test_clocks.advance(reloj.id, delta_seconds=30 * 86400)
print(reloj.id, reloj.current_time, reloj.advanced_seconds)   # NEW id, +30 days

mismo = client.sandbox.test_clocks.get(reloj.id)
print(mismo.frozen_time, mismo.current_time, mismo.status)    # ready

The clock looks like this (in cURL, create and advance return it inside the write envelope’s data, and get returns it directly; the SDKs unwrap the envelope for you):

JSON
{
  "id": "tcl-MTc5MDgxMjgwMC4yNTkyMDAw-9fd1bf0d88220638",
  "object": "test_clock",
  "status": "ready",
  "frozen_time": "2026-10-01T00:00:00+00:00",
  "current_time": "2026-10-31T00:00:00+00:00",
  "advanced_seconds": 2592000,
  "drives": ["loan_aging_dpd", "provisioning_rollforward", "subsidy_permanence"],
  "environment": "sandbox"
}
FieldWhat it is
idThe clock. It changes on every advance.
frozen_timeThe date you froze it on when you created it. It doesn’t change.
advanced_secondsTotal seconds advanced since frozen_time.
current_timefrozen_time + advanced_seconds: the “now” of your scenario.
drivesWhat it’s designed for: delinquency aging (loan_aging_dpd), provisions (provisioning_rollforward) and subsidy permanence (subsidy_permanence).

The clock is your source of 'now', not the gateway's

In this version no other sandbox operation receives a clock id: the sandbox’s loan portfolio, health and PQRS dates are fixed (for example, the first installment of the test loans is due on July 15, 2026). Use current_time as the “now” of your system during the test (inject it into your date service) and compare it with the dates the sandbox returns.

Use case: loan delinquency #

You want to test that your system correctly classifies an installment’s delinquency by days past due (0, 30, 60, 90 days or more). The first installment of the test loans is due on July 15, 2026. Freeze a clock on that day and move it forward in stages; since each call allows at most 63 days, the example advances in jumps and evaluates your classification function at each current_time.

Advance 95 days in jumps of at most 63
from datetime import datetime

MAX = 63 * 86400
reloj = client.sandbox.test_clocks.create(frozen_time="2026-07-15T00:00:00Z")
vence = datetime.fromisoformat(reloj.frozen_time)

pendiente = 95 * 86400
while pendiente > 0:
    paso = min(pendiente, MAX)
    reloj = client.sandbox.test_clocks.advance(reloj.id, delta_seconds=paso)
    pendiente -= paso
    dias = (datetime.fromisoformat(reloj.current_time) - vence).days
    print(reloj.current_time, dias, clasificar_mora(dias))     # your function

Combine it with the test loan that’s 95 days past due (CC 1000000004, status DEFAULTED) to compare your calculation with what the sandbox loan portfolio returns.

Other typical scenarios:

  • Due dates: freeze the clock the day before an obligation is due, advance 1 day and check that your system generates the notice or the late fee.
  • Reminders: advance in 24-hour jumps and check that your scheduled job sends each reminder only once.
  • Permanence: advance months (in jumps of up to 63 days) to test rules that require a minimum membership period.

Errors #

HTTPWhen
403The key is a production key.
422frozen_time is empty or is neither ISO-8601 nor Unix seconds; the id isn’t a clock (tcl-…) or was tampered with; delta_seconds is negative, not an integer or greater than 63 days.

SDK notes #

  • Python: advance(..., by_seconds=...) is the deprecated (1.0) name for delta_seconds; advance(..., to=...) doesn’t exist in the gateway and raises CompenseConfigError; create(name=...) is ignored with a warning (clocks don’t have names). The model is TestClock (id, frozen_time, current_time, advanced_seconds, status, raw).
  • JavaScript / TS: create({ frozenTime }) accepts ISO-8601 text or Unix seconds.
  • PHP: create() accepts text, Unix seconds (int) or \DateTimeInterface.
  • .NET: CreateAsync has overloads for string, long (Unix seconds) and DateTimeOffset; AdvanceAsync(id, deltaSeconds) takes a long.
  • Go: CreateTestClockParams.FrozenTime is a time.Time (sent as RFC 3339 in UTC); AdvanceTestClockParams.DeltaSeconds is an int64.
  • CLI: compense clock create|get|advance (see The compense CLI).