Start, pause, resume, and reset a timer
Duration timers are the only type you can control after creation, and POST /timers/{timer_id}/actions is how. Four actions, one field, and the change appears on the hosted page and every embed immediately. A fixed-date countdown cannot be paused — its deadline is a moment on the calendar, so you move it by updating deadline_at instead.
The call
One field, one of four values. No Idempotency-Key and no If-Match — actions do not create anything and do not carry the lost-update risk that updates do.
Pause a running timer
curl -X POST \
https://countdownshare.com/api/v1/timers/$TIMER_ID/actions \
-H "Authorization: Bearer $COUNTDOWNSHARE_API_KEY" \
-H "Content-Type: application/json" \
-d '{ "action": "pause" }'| action | Valid from | Effect on remaining time |
|---|---|---|
start | scheduled | Begins counting down from the full duration_seconds. |
pause | running | Freezes remaining time at its current value. Nothing is lost. |
resume | paused | Continues from exactly where it froze. |
reset | any state | Returns to the full duration and stops. Send start again to run it. |
resume on something that is running, or any action on a fixed-date countdown — returns HTTP 400 with validation_error. Read the current state from the status endpoint first if you are not certain what it is.The state machine
A new duration timer is scheduled — created but not counting. It stays there until something starts it, which is the behaviour you want when the clock should begin at a moment you choose rather than at the moment the record was created.
Duration timer lifecycle
create start pause resume
| | | |
v v v v
scheduled -------> running -------> paused -------> running
| |
| reset |
+---------------+----------------+
|
v
scheduled
(back to the full duration)
running --- duration elapses ---> endedreset is the only action valid from any state, including ended. It is how you reuse a timer rather than creating a new one — worth knowing, because reuse is free and creation counts against your monthly allowance.
What pausing actually does
It freezes the remaining time, not the deadline. The distinction matters: a timer paused with 4 minutes left and resumed an hour later still has 4 minutes left, not minus 56.
| Pause and resume | Reset | |
|---|---|---|
| Remaining time after | Exactly what it was | The full duration_seconds |
| Resulting state | running | scheduled — it does not auto-start |
| Valid from | running / paused | Any state, including ended |
| Webhook event | timer.paused / timer.resumed | timer.reset |
What viewers see
Actions propagate to every output, but not all of them behave identically because an email client is not a browser.
Hosted page
Reflects the change immediately. A paused countdown stops on screen for anyone watching.
Website embed
Same as the hosted page — it is the same renderer inside an iframe.
Email GIF
Uses the timer state at the moment the inbox requests it. A running timer returns a 30-frame animated GIF; a paused or reset one returns a single-frame GIF.
The email behaviour follows from how email works: the GIF is generated when it is fetched, and it cannot update afterwards. Pausing a timer does not change a GIF already rendered in an inbox — it changes what the next fetch produces. More on this under embeds and hosted pages.
Every action emits an event
Which means you can drive your own logic from a control action without polling. Attach a rule with condition.field: "action" and the value you care about.
| Action | Event type | A reason to listen |
|---|---|---|
start | timer.started | Mark the stock as reserved the moment the hold begins. |
pause | timer.paused | Alert an operator that a session has stalled mid-checkout. |
resume | timer.resumed | Resume a matching process on your side. |
reset | timer.reset | Log that the window was extended, and by whom. |
— (reaches zero) | timer.completed | Release the reservation. This is the one that matters most. |
timer.completed is not an action — it fires on its own when the countdown reaches zero. Listening for it is almost always better than polling the status endpoint waiting for ended, because it arrives at the moment it happens rather than at your next poll. See webhook rules.A worked example
A cart reservation is the canonical case: the clock should start when the customer commits, stop while you are waiting on a third party, and reset cleanly on a retry.
Checkout hold with pause during authorisation
// A checkout hold: 15 minutes, paused while payment is
// authorising so the customer is not charged for our latency.
// 1. Reserve the stock and start the clock.
await action(timerId, "start");
// 2. The payment provider is taking its time — freeze the clock.
await action(timerId, "pause");
const result = await chargeCard(order);
if (result.requiresAction) {
// 3a. 3-D Secure. Hand the remaining time back, unchanged.
await action(timerId, "resume");
} else if (result.failed) {
// 3b. Give the full window back for a retry with another card.
await action(timerId, "reset");
await action(timerId, "start");
}
async function action(id, name) {
return call(`/timers/${id}/actions`, {
method: "POST",
body: JSON.stringify({ action: name }),
});
}Other places this shape fits
- Online exams — pause for a technical issue, resume with the candidate's time intact.
- Support SLAs — the clock stops while a ticket is waiting on the customer.
- Live events and workouts — interval timers a presenter or instructor controls from their own tooling.
- Booking holds — the seat is yours for ten minutes, and reset gives a fresh window without a new timer.