Theme
Connections
A Connection is a webhook endpoint the App POSTs your location to. You can configure as many as you like; each point is fanned out to every active Connection simultaneously.
Fields
| Field | Type | Required | Notes |
|---|---|---|---|
label | string | yes | User-facing name. Shown in the Connections list. |
url | string | yes | http:// or https:// URL. http triggers a one-time cleartext warning on save. |
token | string | no | Sent as Authorization: Bearer <token>. Omitted entirely if empty. |
start | datetime | yes | Window start (UTC). |
end | datetime | no | Window end (UTC). null = no expiry. |
enabled | bool | yes | User toggle. false overrides the window. |
metadata | map<string,string> | yes | Sent as meta in every POST body. {} when empty. |
A Connection is active when enabled is true and the current time is within [start, end]. Only active Connections receive points.
Adding a Connection
Three ways, all producing the same result:
- Manual entry — Connections screen → + → fill the form → Save.
- Config file — Connection form → Import from file → pick a JSON file.
- Deep link — open
withtracker://connection?c=<base64-JSON>. The form opens pre-filled; you must review and confirm before saving.
Duplicate URLs are rejected on save — edit the existing Connection instead.
Config schema
Used by both file import and deep link. id and enabled are not in the schema — id is generated on import, enabled defaults to true.
json
{
"version": 1,
"label": "Fleet North",
"url": "https://fleet.example.com/track",
"token": "abc123",
"start": "2026-08-14T00:00:00",
"end": "2026-08-20T23:59:59",
"metadata": { "deviceId": "BP22" }
}| Field | Required | Default |
|---|---|---|
version | yes | — (must be 1) |
label | yes | — |
url | yes | — |
token | no | null |
start | no | now (on import) |
end | no | now + 90 days (on import); null means no expiry |
metadata | no | {} |
Dates are ISO 8601. Timezone is optional; naive dates are treated as local and converted to UTC internally. An already-expired end is not an error — the form fills and shows a warning.
Deep link format
Two variants, both carrying the same base64-encoded config JSON:
Custom scheme (works everywhere, no domain required):
withtracker://connection?c=<base64-encoded-config-JSON>Universal Link / App Link (opens the App when installed, falls back to the website when not):
https://withtracker.com/open/connection?c=<base64-encoded-config-JSON>Typical length is ~265 characters — well under Android/iOS URL limits and QR-code-friendly. Deep links pre-fill the form; they do not auto-import.
The Universal Link variant requires the App to be associated with the withtracker.com domain via apple-app-site-association (iOS) and .well-known/assetlinks.json (Android), both served from this site.
Wire format
Each recorded location is POSTed to every active Connection as a single JSON body with a fixed schema:
json
{
"id": "550e8400-e29b-41d4-a716-446655440000",
"timestamp": "2026-08-14T10:00:00.000Z",
"meta": { "deviceId": "BP22" },
"data": {
"latitude": 40.7128,
"longitude": -74.0060,
"speed_mps": 12.5,
"bearing": 90.0,
"isMoving": true
}
}| Field | Type | Notes |
|---|---|---|
id | string (uuid v4) | Generated once per point. Reused across all Connections — the same point sent to N webhooks has the same id in all N POSTs. Identifies the point, not the delivery. |
timestamp | string | ISO 8601 UTC with Z and milliseconds. |
meta | object | The Connection's metadata. Always present; {} when empty. |
data.latitude | double | WGS84 degrees. |
data.longitude | double | WGS84 degrees. |
data.speed_mps | double | null | Meters per second. null means unknown (distinct from 0, which means stationary). |
data.bearing | double | null | Degrees 0–360. null means unknown (distinct from 0, which means north). |
data.isMoving | bool | Whether the device is currently moving, per motion detection. |
HTTP details
- Method:
POST - Content-Type:
application/json; charset=utf-8 - Authorization:
Bearer <token>— included only whentokenis non-empty; omitted otherwise (never an empty bearer). - User-Agent:
WithTracker/<version> (<platform>)— e.g.WithTracker/1.0 (android). - Timeouts: 10s connect, 10s send, 15s receive.
- Retry: none. Each POST is fire-and-forget. A failed or timed-out POST is lost.
Example receiver
A minimal endpoint that logs points:
python
from flask import Flask, request, jsonify
app = Flask(__name__)
@app.post("/track")
def track():
body = request.get_json(force=True)
print(f"point {body['id']} @ {body['timestamp']}: "
f"{body['data']['latitude']}, {body['data']['longitude']}")
return "", 204
if __name__ == "__main__":
app.run(port=8080)Validate the Authorization header against your expected bearer token before trusting a point. The App does not sign requests — the bearer token is the only authentication.
