Use server-derived time when client time is untrusted
Use a server-derived receipt as an external comparison point when client time can be manipulated or misconfigured. Keep it separate from client-claimed event time, and do not treat it as signed evidence that the event occurred.
The unsafe pattern
In this threat model, a client-controlled wall clock can be changed, misconfigured, stale, or unavailable. That does not mean every client timestamp is false; it means the current Worker cannot verify the device clock or when the action actually occurred.
The unsafe example stores only what the client clock reported, erasing the distinction between a client claim and independently recorded application context.
Keep five time concepts distinct
SpyderGoat does not capture request-arrival time. Its value is a Worker clock read during response construction, not server-arrival time.
- Event time: when the external action actually occurred; the API does not observe it.
- Client-claimed time: what the client reports from its own clock.
- Application receipt time: when your application records receipt of the client payload.
- Response-construction time: when the Worker reads Date.now() while constructing the HTTP response.
- Receipt time at the caller: when the caller receives the response after network and processing delay.
Keep three time meanings separate
| Field | Meaning | What it does not establish |
|---|---|---|
| client_claimed_at | Client-claimed time reported by the client | Correct device time or event proof |
| application_received_at | Application receipt time recorded by your application | Original event time |
| external_receipt | SpyderGoat response fetched during the application flow | Signed evidence, request-arrival time, or exact event time |
Correct the storage pattern
Validate the canonical response fields and store all three values under separate names. Fetching the receipt during this flow adds an external observation; it does not move or certify an earlier event time.
The free KV-conditional 30-minute limit is shared across /mjd and /jd and makes a fresh receipt unsuitable for rapid or high-frequency event sampling.
Threat and failure boundary
- A receipt can be copied into another record or replayed with a later payload.
- Network delay separates the client event, application receipt, response construction, and caller receipt.
- Offline clients cannot obtain a fresh HTTP receipt; preserve their local event data and mark external time unavailable.
- A timeout, 429, malformed JSON, or service unavailable outcome must not be replaced with invented external time.
- Your application decides whether failure blocks the operation or is stored as an explicit unavailable state.
- A delayed, queued, copied, or replayed response cannot establish event time.
Not tamper-proof and not event proof
The JSON is not request-bound, not nonce-bound, not data-bound, not event-bound, and not signed. It can be copied or replayed and is not independently verifiable after copying.
SpyderGoat does not make client input tamper-proof, prevent replay, authenticate the event, or prove that an external event happened at the returned time. The literal source marker "server" adds none of those guarantees.
Examples
Unsafe client-only timestamp
const event = {
action: "approved",
occurredAt: new Date().toISOString(),
};Corrected request pattern
const clientEvent = {
action: "approved",
occurredAt: "2026-03-08T09:07:18.000Z",
};
async function addTimeContext(event) {
const applicationReceivedAt = new Date().toISOString();
const controller = new AbortController();
const timer = setTimeout(() => controller.abort(), 5000);
try {
const response = await fetch("https://spydergoat.com/mjd", {
headers: { Accept: "application/json" },
signal: controller.signal,
});
if (response.status === 429) {
throw new Error("SpyderGoat rate limited; Retry-After=" + response.headers.get("Retry-After"));
}
if (!response.ok) {
throw new Error("SpyderGoat HTTP " + response.status);
}
const externalReceipt = await response.json();
const receiptMs = Date.parse(externalReceipt?.utc);
if (
externalReceipt === null ||
typeof externalReceipt !== "object" ||
!Number.isFinite(externalReceipt.mjd) ||
typeof externalReceipt.utc !== "string" ||
!Number.isFinite(receiptMs) ||
new Date(receiptMs).toISOString() !== externalReceipt.utc ||
!Number.isSafeInteger(externalReceipt.unix) ||
externalReceipt.source !== "server"
) {
throw new Error("Unexpected SpyderGoat response");
}
return {
client_claimed_at: event.occurredAt,
application_received_at: applicationReceivedAt,
external_receipt: externalReceipt,
};
} finally {
clearTimeout(timer);
}
}
console.log(await addTimeContext(clientEvent));Illustrative stored schema
Schema illustration only; this is not evidence of a real event.
{
"client_claimed_at": "2026-03-08T09:07:18.000Z",
"application_received_at": "2026-03-08T09:07:21.700Z",
"external_receipt": {
"mjd": 61107.38011574,
"utc": "2026-03-08T09:07:22.000Z",
"unix": 1772960842,
"source": "server"
}
}Limits and non-guarantees
- What this receipt does not guarantee: SpyderGoat returns a server-derived HTTP time receipt derived from the Cloudflare Worker runtime clock. The Worker clock is read during response construction. It is an unsigned HTTP receipt, not NTP, an atomic-clock feed, a signed timestamp, or an RFC 3161 authority or timestamp token. It does not synchronize or discipline your clock, and it does not prove when an external event occurred. Displayed precision is not an accuracy guarantee. HTTP and network latency affect comparisons. SpyderGoat publishes no uptime or accuracy SLA.
- The receipt is not bound to a request, nonce, record, payload, or event and cannot establish the ordering or occurrence of external actions.
Runtime provenance
This page describes current behavior defined by buildTimeResponse, checkFreeRateLimit, and request routing in src/api/worker.js, with public API behavior asserted in test/worker.test.js. The value source: "server" is a literal marker added by the Worker. It is not a signature, authentication, origin proof, or clock-quality attestation.