Skip to content

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 ​

FieldTypeRequiredNotes
labelstringyesUser-facing name. Shown in the Connections list.
urlstringyeshttp:// or https:// URL. http triggers a one-time cleartext warning on save.
tokenstringnoSent as Authorization: Bearer <token>. Omitted entirely if empty.
startdatetimeyesWindow start (UTC).
enddatetimenoWindow end (UTC). null = no expiry.
enabledboolyesUser toggle. false overrides the window.
metadatamap<string,string>yesSent 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:

  1. Manual entry — Connections screen → + → fill the form → Save.
  2. Config file — Connection form → Import from file → pick a JSON file.
  3. 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" }
}
FieldRequiredDefault
versionyes— (must be 1)
labelyes—
urlyes—
tokennonull
startnonow (on import)
endnonow + 90 days (on import); null means no expiry
metadatano{}

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.

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
  }
}
FieldTypeNotes
idstring (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.
timestampstringISO 8601 UTC with Z and milliseconds.
metaobjectThe Connection's metadata. Always present; {} when empty.
data.latitudedoubleWGS84 degrees.
data.longitudedoubleWGS84 degrees.
data.speed_mpsdouble | nullMeters per second. null means unknown (distinct from 0, which means stationary).
data.bearingdouble | nullDegrees 0–360. null means unknown (distinct from 0, which means north).
data.isMovingboolWhether the device is currently moving, per motion detection.

HTTP details ​

  • Method: POST
  • Content-Type: application/json; charset=utf-8
  • Authorization: Bearer <token> — included only when token is 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.

Made by Eagle Logistics.