# Testing your integration – Tiempo

> Test a Tiempo integration before going live: test mode, the dashboard harness, races and holds that expire, idempotent retries, webhooks on your machine, and isolated test data in CI.

Source: https://tiempo.one/docs/testing

Test mode is the real thing with test data: holds expire, races are refused, webhooks are signed and retried. Nothing in it is billed, and nothing you do there can touch a live business. Use a test key (`tpt_…`) everywhere except production.

The things worth testing are the ones that only happen under pressure: two customers at once, a customer who leaves, a network retry, a webhook that arrives twice.

## Try it in the dashboard first

The [test harness](https://tiempo.one/app/harness) walks through a business, staff, hours, a search, a hold and a confirmation using real API requests, then tries to double-book the slot and shows the refusal. Every request it sends is listed, and each can be copied as curl.

## Races

Send two holds for the same resource and time in parallel. Exactly one succeeds; the other is `409 booking_conflict`. Your app should show the customer other times, not an error page, and should never retry the same slot in a loop.

The same goes for “anyone available”: two parallel holds with the same candidate list get different resources, or one gets a 409 when nobody is left.

Shell

```bash
# Two customers grab the same barber at the same moment
for i in 1 2; do
  curl -s -o /dev/null -w "%{http_code}\n" -X POST "$TIEMPO/v1/bookings" \
    -H "Authorization: Bearer $TIEMPO_KEY" \
    -H "Tiempo-Account: $ACCOUNT" \
    -H "Content-Type: application/json" \
    -d '{"segments": [{"resource": "res_ann…",
         "start": "2026-11-16T10:00:00Z", "end": "2026-11-16T10:45:00Z"}]}' &
done
wait
# 200
# 409
```

## Customers who leave

Create a hold with an `expires_at` a minute away, don’t confirm it, and check that your app releases the customer’s basket when `booking.expired` arrives. Then try confirming it late: you get a `409`, which is what stops a slow payment from taking a slot someone else has since booked.

A hold that lapses in a minute

```bash
curl -X POST "$TIEMPO/v1/bookings" \
  -H "Authorization: Bearer $TIEMPO_KEY" \
  -H "Tiempo-Account: $ACCOUNT" \
  -H "Content-Type: application/json" \
  -d '{
    "segments": [
      {
        "resource": "res_ann…",
        "start": "2026-11-16T11:00:00Z",
        "end": "2026-11-16T11:45:00Z"
      }
    ],
    "expires_at": "2026-11-15T09:31:00Z"
  }'
```

## Retries

Send the same POST twice with the same `Idempotency-Key`. The second answer is the first one replayed, with `Idempotent-Replayed: true`, and there’s still one booking. Make your keys from your own records (`apt_123-hold`, `apt_123-confirm`) so a retry after a crash reuses them.

## Webhooks on your machine

Tiempo only delivers to public addresses, so expose your local server with a tunnel (ngrok, Cloudflare Tunnel or similar) and register the tunnel’s https URL as a test-mode endpoint. Or skip webhooks while you build and read the same events from `GET /v1/events`.

Test that your handler rejects a bad signature, ignores a delivery it has already processed (same event `id`), and replies within 10 seconds. The dashboard can resend any event to an endpoint.

Signature check, as a unit test (Node)

```js
import crypto from 'node:crypto'
import { test } from 'node:test'
import assert from 'node:assert'
import { verify } from './webhooks.js' // your handler's check

test('accepts a correctly signed delivery and rejects a tampered one', () => {
  const secret = 'tpw_test_secret'
  const body = JSON.stringify({ id: 'evt_1', type: 'booking.confirmed' })
  const t = Math.floor(Date.now() / 1000)
  const v1 = crypto.createHmac('sha256', secret).update(`${t}.${body}`).digest('hex')
  assert.ok(verify(body, `t=${t},v1=${v1}`, secret))
  assert.ok(!verify(body + ' ', `t=${t},v1=${v1}`, secret))
})
```

## In CI

-   Keep a separate platform (and test key) for CI, in your CI secrets, so its data never mixes with anyone’s manual testing.
-   Create a fresh account per test run. Accounts are isolated, so parallel runs can’t see each other’s bookings, and a failed run leaves nothing behind that breaks the next.
-   Book a few days ahead and search from “now”: past times are never offered, so tests pinned to a fixed date start failing once that date has passed.

The machine-readable description is at [/openapi.json](https://tiempo.one/openapi.json). Try calls in [Swagger UI](https://tiempo.one/swagger-ui.html).[Tiempo home](https://tiempo.one/)[Dashboard](https://tiempo.one/app)
