Webhooks
Paid plans Get a signed HTTPS request the moment something happens on your account — an SOS, a check-in, a zone crossing — and, if you turn it on, the positions of the watches on your map. Pipe it into your own dashboard, n8n, Zapier, a chat bot or anything that accepts a POST.
- Open your dashboard → Integrations → Webhooks and add your HTTPS URL.
- Copy the signing secret (
whsec_…) — it is shown once. - Pick the events you want, or leave “All activity events” on.
- Press Send test and check the delivery log.
Events
sos.raised | Someone in your family raised an SOS |
sos.sent | You raised an SOS |
sos.response | Someone responded to an SOS |
check_in.received | A family member checked in |
check_in.sent | You checked in |
zone.entered | A watch entered a home zone |
zone.left | A watch left a home zone |
waypoint.marked | A waypoint was marked from a watch |
family.invite | You were invited to a family |
family.member_left | A member left your family |
trip.ended | A trip you joined was ended |
position | A watch you can see reported a new position (opt-in, at most every 5 minutes per watch) |
ping | Sent by “Send test” |
Payload
Every request is a POST with a JSON body of type, timestamp and data.
{
"type": "sos.raised",
"timestamp": "2026-09-14T19:02:11Z",
"data": {
"activity_id": 48213,
"code": "sos_raised",
"severity": "error",
"title": "SOS from Sam",
"body": "Sam raised an SOS near Refuge du Goûter"
}
}{
"type": "position",
"timestamp": "2026-09-14T19:05:40Z",
"data": {
"device_id": "pixel-watch-3-8f2c",
"device_name": "Sam's Pixel Watch",
"member_name": "Sam",
"lat": 45.8508, "lng": 6.8127,
"accuracy_m": 6, "altitude_m": 3817,
"speed_mps": 0.9, "battery_pct": 71,
"sos": false,
"recorded_at": "2026-09-14T19:05:38Z"
}
}Verifying the signature
Requests are signed to the Standard Webhooks spec, so the official libraries for Node, Python, Go, PHP, Ruby, Java and more verify them out of the box. Three headers come with every request: webhook-id (the same on every retry — use it to ignore duplicates), webhook-timestamp and webhook-signature (v1, + base64 HMAC-SHA256 of id.timestamp.body).
// npm i standardwebhooks
import { Webhook } from 'standardwebhooks'
const wh = new Webhook(process.env.WEBHOOK_SECRET) // "whsec_…"
app.post('/hooks', express.raw({ type: 'application/json' }), (req, res) => {
const event = wh.verify(req.body, req.headers) // throws if the signature is wrong
res.sendStatus(204) // answer fast, work later
})import base64, hashlib, hmac, time
def verify(secret: str, headers, body: bytes) -> bool:
msg_id, ts = headers["webhook-id"], headers["webhook-timestamp"]
if abs(time.time() - int(ts)) > 300:
return False # replayed or stale
key = base64.b64decode(secret.removeprefix("whsec_"))
mac = hmac.new(key, f"{msg_id}.{ts}.".encode() + body, hashlib.sha256)
expected = base64.b64encode(mac.digest()).decode()
return any(hmac.compare_digest(expected, s.split(",", 1)[1])
for s in headers["webhook-signature"].split())Delivery
- Answer with any 2xx within 15 seconds. Redirects are not followed.
- Anything else is retried after 1 min, 5 min, 30 min, 2 h, 6 h and 12 h — seven attempts in all.
- After 20 failures in a row the webhook is switched off and we email you. Fix it, send a test, and enable it again.
- Only public
https:// URLs are accepted, up to 50 webhooks per account. - If an endpoint falls more than 500 events behind, newer events for it are skipped until it catches up.
- The delivery log in your dashboard keeps 7 days of requests, with the payload and the response code.
Developer SDK
Family Plus Build your own apps on your family’s data: last known positions, location history, trails and recordings, zones and who is in them, the activity feed, and a live stream of positions and events (SOS, check-ins, zone arrivals). Read-only — the SDK can’t move, message or wake anyone.
- Open your dashboard → Integrations → Developers and create an SDK key (
tsk_…). It is shown once. - Your code trades the key at
POST /api/sdk/v1/token for a bearer token (a JWT valid for 15 minutes). - Call
/api/sdk/v1/… with Authorization: Bearer <token>. The Go SDK does steps 2 and 3 for you.
Go
import paceguard "paceguard.io/sdk"
c := paceguard.New(os.Getenv("PACEGUARD_SDK_KEY"),
paceguard.WithBaseURL("https://trail-safe.app"))
positions, err := c.Positions(ctx) // every family device's last fix
// live positions + activity; renews the token and reconnects by itself
err = c.Stream(ctx, paceguard.StreamOptions{}, func(ev paceguard.Event) error {
fmt.Println(ev.Type)
return nil
})Python
import os, requests
BASE = "https://trail-safe.app"
tok = requests.post(f"{BASE}/api/sdk/v1/token",
headers={"Authorization": f"Bearer {os.environ['PACEGUARD_SDK_KEY']}"}).json()["access_token"]
positions = requests.get(f"{BASE}/api/sdk/v1/positions",
headers={"Authorization": f"Bearer {tok}"}).json()["positions"] JavaScript works the same way with fetch; the dashboard shows a ready-to-copy example for Go, Python and JavaScript next to your key.
Endpoints
GET /api/sdk/v1/positions | Last known position of every family device |
GET /api/sdk/v1/devices | Watches and phones you can see |
GET /api/sdk/v1/devices/{id}/locations | Location history (since, until, limit) |
GET /api/sdk/v1/devices/{id}/trails | Trails of a device |
GET /api/sdk/v1/tracks | Your recordings; /tracks/{id}/geometry for the points |
GET /api/sdk/v1/zones | Home, school and other zones, and who is in them |
GET /api/sdk/v1/activity | Activity feed: SOS, check-ins, arrivals |
GET /api/sdk/v1/members | Your family |
GET /api/sdk/v1/stream | Live positions and activity (Server-Sent Events) |
Limits
- One SDK key per account, and one active session per key: a new token ends the previous one (
session_replaced). Run one program per key. - One open live stream per account. The stream ends when its token expires; reconnect with a new one.
- Optionally lock a key to your own IP addresses or networks under Integrations → Developers → Restrict to IPs; calls from anywhere else get
ip_not_allowed. - Revoking the key, or leaving Family Plus, stops its tokens and its stream within a minute.
- Responses never contain IP addresses of your devices.
- Polling the SDK never wakes your family’s phones; only someone opening the map does.
Kids watch
Coming soonBring your child's kids watch onto the family map, with its position and battery — linked from the dashboard on Family plans, where you can also locate it, send it a message and change its alarms and settings.
Strava
Coming soonAuto-publish completed hikes with the recorded track, elevation profile, and heart-rate trace.
Garmin Connect
Coming soonSide-load fixes from a Garmin watch so you can use the hardware you already own.
Apple Health / Health Connect
Coming soonPull in vitals (resting HR, VO2 max) to give your hiking profile better baselines.
Slack / Discord
Coming soonPost departure and arrival notices to a private channel for hiking groups and family threads.
IFTTT / Make
Coming soonNo-code triggers for the long tail — light a smart bulb when you start, send an SMS when you arrive.