Time in the API
Instants are RFC 3339 in UTC with a Z, as in 2026-09-10T09:15:00Z. The webhook signing header is the exception: Unix seconds as a string, because it is compared arithmetically rather than displayed. Verification dates are plain calendar dates. Local time matters in exactly one place — when a person reads a due time or a business-hours window.
Every time value on the public surface#
| Field | Where | Format |
|---|---|---|
occurredAt | Event envelope | RFC 3339 date-time, UTC: 2026-09-10T09:15:00Z |
dueAt and similar inside data | Event envelope | The same RFC 3339 form |
X-Connect-Timestamp | Webhook header | Unix seconds, as a decimal string: 1789045200 |
updated | Page record | Calendar date: 2026-09-10 — no time, no zone |
generated | Manifests | Calendar date, the corpus verification date |
created_at, last_used_at, revoked_at | Integration key description | ISO 8601 with a trailing Z |
Two formats rather than one is deliberate. occurredAt is read by people and by code and benefits from being unambiguous on sight. The signing header is only ever subtracted from the current time, and Unix seconds make that a single comparison with no parser in the path — which matters when it is the check standing between you and a replayed request.
Why dates are dates and not timestamps#
updated and generated carry no time of day, and that is honest rather than lossy. They record the day a page's facts were verified, and verification is a piece of work done on a day, not an event that happened at 14:32:07. A timestamp there would imply a precision nobody has.
The practical consequence: compare them as dates. Parsing 2026-09-10 into a datetime at midnight and then converting it to a local zone will move it to the previous day for anyone west of UTC, which turns 'verified today' into 'verified yesterday' for half your users.
Clocks, windows and skew#
Three time windows appear on the public surface, and they behave differently on purpose.
- The signature freshness window
- Reject an
X-Connect-Timestampfar from now — a few minutes is the guidance. This is the only place where *your* clock being wrong causes valid requests to be rejected, which is a good argument for running NTP on a webhook receiver. - The rate-limit window
- Sixty seconds, measured on a monotonic clock rather than the wall clock. A daylight-saving change or an NTP correction cannot move it, and cannot accidentally grant or withdraw a caller's allowance.
- The cache window
max-age=300. Five minutes of a previous build being served is five minutes of something that was true; the corpus changes on a deploy, not on a request.
None of these needs your timezone. Everything published is UTC or zone-free, so a client that stores UTC and converts only at the point of display never has a conversion bug to find.
The one place local time is the truth#
When a person reads a time, the business's own timezone is the correct one — a follow-up due 'tomorrow morning' means morning where the business is, and business hours are a local-clock concept by definition. A call that arrives outside them is outside them locally, not at some UTC offset.
That is a presentation and scheduling concern inside the workspace rather than a wire-format one. The wire stays UTC; the interpretation is local. Keeping those apart is what stops a follow-up from drifting an hour twice a year.
So in your client: store the instant, render in the reader's zone, and do not attempt to re-derive a business's local intent from a UTC value alone. The instant is a fact; the intent belongs to the workspace that set it.
Questions#
Can I rely on occurredAt being unique per event?
No. Two events can share an instant. id is the identity; occurredAt is when it happened, and it is the right field to order by — with id as a tiebreak if you need a total order.
What if my clock is ahead of Connect's?
A moderate skew is absorbed by the few-minute freshness window. A large one makes every signed request look stale and every verification fail, which is why the symptom of a broken receiver clock is 'everything is rejected' rather than an intermittent fault.
Does the API accept a timezone parameter?
There is nothing to parametrise: the public routes are documentation reads and return dates and instants as published. Conversion belongs in your client.