Asking questions

Rules and webhooks

A rule is a standing question. Instead of asking "are they home yet?" every thirty seconds, you register the question once and we POST to you when the answer changes.

Creating a rule

POST /v1/rules/place
Authorization: Bearer cka_…
Content-Type: application/json

{
  "type": "enter",
  "place_id": "9f2c…"
}
{
  "id": "3d81…",
  "type": "enter",
  "target": { "place_id": "9f2c…", "label": "Home" },
  "dwell_minutes": null,
  "webhook_url": null,
  "disabled_at": null,
  "created_at": "2026-08-24T09:00:00.000Z"
}

Where it is delivered. When the rule fires, the event goes to your app's webhook endpoints that subscribe to rule.fired, signed with each endpoint's secret. Register an endpoint in the portal before you create rules; a rule has no secret of its own.

webhook_url is optional. Leave it out, or set it to the URL of one of your registered endpoints to send this rule's events there. Any other URL is refused with 400 webhook_url_not_registered — so a leaked access token can create a rule, but cannot send a user's arrivals anywhere you did not register.

Use /v1/rules/zone for your own geometry:

{
  "type": "exit",
  "lat": 37.7749,
  "lon": -122.4194,
  "radius_m": 500,
  "label": "Warehouse 5"
}

Same limits as verify-zone: radius_m is 100–10,000 m, and label is required and shown to the user each time the rule fires.

GET either path to list your rules. DELETE /v1/rules/place/{id} removes one.

Windows, and the cap

A rule may carry an active window: active_from and active_until, ISO 8601 timestamps, either one optional. Outside its window the rule is not evaluated at all, and once the window has closed it is finished for good. Use this for anything tied to a date — a hotel on the 20th, a flight on the 22nd — so the rule cannot keep watching the hotel in October.

{
  "type": "enter",
  "lat": 41.9028,
  "lon": 12.4964,
  "radius_m": 300,
  "label": "Hotel Artemide",
  "active_from": "2026-09-19T12:00:00Z",
  "active_until": "2026-09-21T00:00:00Z"
}

A connection may have at most ten rules active at the same instant. The cap is on concurrency, not on the total: a two-week itinerary of twelve one-day windows never has more than one or two active at once and fits in a single slot, while ten standing rules with no window fill the connection. Creating a rule that would push any instant past ten is a 409 that names the instant. A window that has already closed, or that ends before it starts, is a 400.

A windowed rule sees only fixes taken inside its window — judged by when the fix was taken, not when it was uploaded — so a batch that lands up to a day after the window closes is still applied, and a fix from before it opened never seeds the rule's state. The usual freshness rule still holds: a fix more than 15 minutes old updates state silently rather than firing.

typeFires whenNeeds
enterthey cross from outside to inside—
exitinside to outside—
dwellinside continuously for N minutesdwell_minutes (5–720)

dwell fires once per stay. Leaving and coming back starts a new one.

When a rule will not fire

Four rules decide this, and each one produces silence that looks like a bug.

A new rule needs two readings. The first location we see after you create a rule only establishes where the user is. One reading can't tell us where they came from, so it can never be a crossing.

Readings near the edge don't count. If a fix is within max(50 m, its accuracy) of the boundary, it's boundary, and boundary readings leave the state untouched. This stops someone standing on the edge producing enter, exit, enter, exit. It also means a zone must be comfortably bigger than the margin, or no reading is ever cleanly inside it.

The same rule won't fire twice within two minutes. A quiet cooldown, so a noisy phone can't produce a burst of notifications.

Old readings update state silently. A crossing computed from a fix more than 15 minutes old sets the state but sends nothing. Without that, a phone reconnecting after a day offline would replay yesterday's arrivals.

If a rule is silent when you expected an event, work through those four before assuming something is broken.

Rules are not the only thing we POST to you — see Webhook events for every event we send, including places.changed, which tells you the set of places shared with you has moved.

Receiving a delivery

{
  "event_id": "b2f0…",
  "app_id": "a19d…",
  "endpoint_id": "e5b2…",
  "rule_id": "3d81…",
  "grant_id": "7c4a…",
  "type": "place.enter",
  "occurred_at": "2026-08-24T09:15:00.000Z",
  "target": { "place_id": "9f2c…", "label": "Home" }
}

type is place. or zone. plus the rule type. There are no coordinates in the payload. An event says someone entered "Home", never where Home is, so you can't rebuild a track from your own notifications.

Rule deliveries are signed, retried and disabled like every other event. The mechanics are on Webhook events: respond 2xx quickly, verify the signature over the raw body (accepting any matching v1 during a secret rotation), deduplicate on event_id to stop replays, and expect retries: within the retry window (about a day) an event can arrive more than once, and a receiver down for longer than that can miss one, so delivery is not guaranteed overall.

Before you drive something physical

Read this if a rule will unlock a door, disarm an alarm, or anything else with a consequence.

An event says what happened, not what is true now. An event can fire from a fix up to 15 minutes old, and retries can add up to a day. So place.enter can reach you twenty minutes — or hours — after someone walked in — by which time they may have locked up behind them and gone to bed. Unlocking then leaves the door open while they believe it's locked.

Do both of these.

Cap the age

{
  "type": "enter",
  "place_id": "9f2c…",
  "max_event_age_s": 90
}

We refuse to deliver that event once it's older than 90 seconds, including on retries. The user sees the withheld event in their log, so a dropped notification is still accountable.

Accepts 30–3,600 seconds. Omit it and events deliver at any age, which is right for logs and notifications.

Check before acting

The cap limits staleness; it doesn't remove it. Treat the webhook as a reason to go and look:

GET /v1/answers/places/{place_id}/presence?max_age_s=60

Act only on state: "yes". no means they've already left. unknown means you don't know — and for a lock, not knowing means not unlocking.

The rule tells you when to look. The question tells you what's true.

How late events are

Events fire as locations arrive from the phone, so expect minutes, not seconds. iOS batches uploads to save battery, and we can't tell you about a crossing before we hear about it. Measured on real arrivals, two to three minutes is typical.

If you need an answer now, ask a question instead of waiting. Rules suit things that can happen a few minutes late: a notification, a log entry, turning on the heating.

Requirements

Deliveries go only to endpoints registered on your app, which must be public HTTPS — see Webhook events. Rules created before app endpoints existed keep their own URL and secret until you delete them; the migration note says how to move them.

Rules belong to the connection that created them. If the user disconnects you, the grant's standard tier expires, or they delete the rule, it stops firing. Rules are standard scopes, so the sensitive tier lapsing does not touch them — see Two expiries. Users see every rule you hold, described plainly: "Tell them when you arrive at Home."

Previous
Derived history