Skip to content

Test Clocks

A test clock simulates the passage of time for a chosen set of sandbox customers. Advance the clock and the billing engine evaluates everything scheduled for those customers as if the clock’s new frozen_time were now — plan renewals, subscription rollovers, dunning retries, payment-link expiry. A month of billing takes seconds instead of a month.

Test clocks are sandbox-only. They never touch live customers or move real money, and requests with a live key are rejected.

How it works

  1. Create a clock with a starting frozen_time (the simulated “now”)
  2. Create customers attached to the clock — everywhere the billing engine asks “what time is it?”, these customers get the clock’s time
  3. Attach billing objects to those customers: payment plans, subscriptions, payment links
  4. Advance the clock to a new frozen_time — the advance runs asynchronously
  5. Observe the effects — the test_clock.ready webhook reports what fired, and the affected objects are in their post-advance states
  6. Repeat as needed within the clock’s 30-day lifetime

Quick start

1. Create a test clock

Terminal window
curl -X POST https://api.elasticpay.co/api/v1/test_helpers/test_clocks \
-H "Authorization: Bearer sk_sandbox_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx" \
-H "Content-Type: application/json" \
-d '{"name": "Monthly renewal walkthrough", "frozen_time": "2026-01-01T00:00:00Z"}'

Response (201 Created):

{
"id": "clk_3kEZpy0UsPbAlvbiGzYEYtSi",
"object": "test_clock",
"name": "Monthly renewal walkthrough",
"status": "created",
"frozen_time": "2026-01-01T00:00:00.000Z",
"expires_at": "2026-02-08T14:20:05.000Z",
"livemode": false
}

expires_at is 30 days after the clock is created (wall clock), not 30 days after frozen_time.

2. Create a customer attached to the clock

Terminal window
curl -X POST https://api.elasticpay.co/api/v1/customers \
-H "Authorization: Bearer sk_sandbox_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx" \
-H "Content-Type: application/json" \
-d '{
"name": "Alex Smith",
"email": "alex@example.com",
"test_clock": "clk_3kEZpy0UsPbAlvbiGzYEYtSi"
}'

The test_clock field can only be set at creation and is immutable — you cannot attach or detach a clock from an existing customer.

3. Advance the clock

Attach a payment plan or subscription to the customer first (see the worked examples below), then advance:

Terminal window
curl -X POST https://api.elasticpay.co/api/v1/test_helpers/test_clocks/clk_3kEZpy0UsPbAlvbiGzYEYtSi/advance \
-H "Authorization: Bearer sk_sandbox_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx" \
-H "Content-Type: application/json" \
-d '{"frozen_time": "2026-02-01T00:00:00Z"}'

Response (202 Accepted) — note the clock still shows the old frozen_time; the advance completes asynchronously:

{
"id": "clk_3kEZpy0UsPbAlvbiGzYEYtSi",
"status": "advancing",
"frozen_time": "2026-01-01T00:00:00.000Z"
}

You’ll receive test_clock.advancing when the advance is accepted, then test_clock.ready when the billing effects have completed — or poll the clock until status is ready.

4. Retrieve the updated clock

Terminal window
curl https://api.elasticpay.co/api/v1/test_helpers/test_clocks/clk_3kEZpy0UsPbAlvbiGzYEYtSi \
-H "Authorization: Bearer sk_sandbox_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"
{
"id": "clk_3kEZpy0UsPbAlvbiGzYEYtSi",
"status": "ready",
"frozen_time": "2026-02-01T00:00:00.000Z",
"last_advanced_at": "2026-01-09T14:23:11.000Z"
}

frozen_time is simulated time; last_advanced_at is the real wall-clock moment the advance completed.

API reference

The test clock object

FieldTypeDescription
idstring (clk_...)Unique identifier
object"test_clock"Always "test_clock"
namestring or nullOptional label for the scenario
statusenumcreated / advancing / ready / failed
frozen_timeISO 8601Simulated current time for the clock’s customers
expires_atISO 860130 days after creation; the clock is auto-deleted then
last_advanced_atISO 8601 or nullWall-clock time of the last completed advance
livemodefalseAlways false — clocks are sandbox-only

Endpoints

MethodPathDescription
POST/api/v1/test_helpers/test_clocksCreate a clock
GET/api/v1/test_helpers/test_clocksList clocks (newest first)
GET/api/v1/test_helpers/test_clocks/:idRetrieve a clock
POST/api/v1/test_helpers/test_clocks/:id/advanceAdvance a clock
DELETE/api/v1/test_helpers/test_clocks/:idDelete a clock and everything attached to it

All endpoints require a sandbox secret key with the test_clocks:read or test_clocks:write scope. A live key returns 403 with code sandbox_only.

Createfrozen_time (required, ISO 8601), name (optional). 422 if you already have 3 active clocks or frozen_time is missing.

Listlimit (default 20, max 100) and starting_after (a clk_... id) for cursor pagination. Returns { "data": [...], "has_more": true|false }.

Advancefrozen_time (required) must be after the current frozen_time and within the advance bound. The clock must be in created or ready status.

Delete — removes the clock and all of its customers, along with their payment plans, payment intents, subscriptions, and mandates. This is irreversible.

The advance bound

A single advance can move time forward by at most two billing intervals of the shortest active plan or subscription attached to the clock’s customers:

max advance target = frozen_time + (2 × shortest_billing_interval)

The interval accounts for the plan’s full frequency — a weekly plan is 7 days, a fortnightly plan 14, a monthly plan 30; subscriptions count as 30 days. With no billing objects attached yet, a default 31-day interval applies, giving a 62-day window. If an advance exceeds the bound, the 422 error states the maximum allowed target time.

Need to go further? Issue multiple advances — each fires its own test_clock.advancing / test_clock.ready pair, so you observe each cycle’s effects incrementally.

Webhook events

Delivered to your sandbox webhook subscriptions only:

EventFires whenNotable payload fields
test_clock.createdClock createdClock object
test_clock.advancingAdvance acceptedClock object + target_time
test_clock.readyAdvance completedClock object + effects_manifest
test_clock.deletedClock deleted (manually or at expiry)Clock object at deletion

There is no failure webhook — if an advance fails, the clock’s status becomes failed (see the FAQ).

The effects_manifest in test_clock.ready summarises what the advance produced:

{
"payment_intents_enqueued": 2,
"links_expired": 1,
"subscriptions_rolled": 1,
"plans_recalculated": 2
}

Worked examples

Test a monthly payment plan renewal

Create clock at Jan 1
Create customer attached to the clock
Create a monthly payment plan for that customer, start_date Jan 1
→ plan activates; first payment scheduled for Jan 1
Advance to Jan 1 noon
→ test_clock.ready: payment_intents_enqueued: 1 — first payment succeeds
Advance to Feb 1
→ test_clock.ready: payment_intents_enqueued: 1 — renewal fired
→ check the payment intent: succeeded; next payment scheduled Mar 1

Test dunning — a payment fails, the retry fires

Create clock at Jan 1
Create customer + plan where the collection will decline
(force the outcome — see Forcing payment outcomes on the Test Cards page)
Advance to Jan 1 noon
→ payment dispatches and fails; the plan's failure handling queues a retry
Advance to Jan 4 (the retry window)
→ test_clock.ready: payment_intents_enqueued: 1
→ check whether the retry succeeded or failed
Create clock at Jan 1, customer attached
Create a payment link expiring Jan 7 — status: active
Advance to Jan 8
→ test_clock.ready: links_expired: 1
→ link status is expired and no longer payable

Test subscription period rollover

Create clock at Jan 1, customer with a monthly subscription
Advance to Feb 1
→ test_clock.ready: subscriptions_rolled: 1
→ current period is now Feb 1 – Mar 1

Limits and lifetime

LimitValue
Customers per clock3
Active clocks per sandbox account3
Clock-bound customers per sandbox account9
Clock lifetime30 days from creation
Advance directionForward only — no rewind
Advance bound per call2 billing intervals

When a clock expires or is deleted, all of its customers and their billing objects are deleted permanently. Design scenarios around this lifetime, or delete and recreate clocks between test sessions.

Not everything moves with the clock — see What Test Clocks Simulate before building scenarios around settlement or BECS collection outcomes.

FAQ

Can I use a live API key? No. test_helpers endpoints reject live keys with 403 sandbox_only.

Can I attach an existing customer to a clock? No — test_clock is set at customer creation and is immutable. Create a new customer on the clock.

Can I rewind? No. Clocks are forward-only. To re-run a scenario, delete the clock (which deletes its customers) and create a fresh one.

What happens at expiry? After 30 days the clock, its customers, and their billing objects are deleted automatically. There is no recovery.

My advance was rejected for exceeding the bound. You asked for a target beyond frozen_time + 2 × shortest interval — the error message includes the maximum allowed time. Advance in smaller steps.

The clock is stuck in advancing or shows failed. A failed clock cannot be advanced again — delete it and recreate the scenario. If a clock stays in advancing unusually long, check its status in the dashboard (Developer → Test Clocks).