Skip to main content

Documentation Index

Fetch the complete documentation index at: https://docs.radar.com/llms.txt

Use this file to discover all available pages before exploring further.

In this tutorial, we show you the Radar components to build a workforce management experience from end to end. You’ll learn how to power the full shift cycle: shift creation, shift experience, and reimbursement workflows. The core shift experience we’ll cover looks like this:
  1. A shift is created
  2. A professional gets assigned a shift
  3. The professional starts traveling to the shift
  4. The professional’s live ETA and location updates are recorded
  5. The professional automatically clocks in upon arrival
  6. The professional automatically clocks out upon departure
  7. The professional is reimbursed either by time onsite and/or trip mileage
Each step in this flow maps directly to a Radar feature such as geofences, trips, and webhooks. Instead of building and maintaining location infrastructure from scratch, your team can focus on the product experience on top of it.

Features used

Shift lifecycle

StepTriggerRadar feature
Shift createdA shift is createdGeofences API: create a geofence at the shift location
Professional assignedThe professional is assigned or picks up a shiftSet user metadata with shift context
Professional en routeThe professional taps “Start route”Trips: start a trip with the shift geofence as the destination
Live ETA trackingThe professional is travelinguser.updated_trip webhook
Approaching alertThe professional is nearbyuser.approaching_trip_destination webhook
Auto clock-inThe professional enters the shift geofenceuser.entered_geofence webhook
Auto clock-outThe professional exits the shift geofenceuser.exited_geofence webhook
Mileage reimbursementThe shift is completedRoute matching API: calculates the trip’s mileage

Getting started

If you haven’t already, sign up for Radar to get your API keys.

Initialize the SDK

For full setup instructions, see the iOS SDK and Android SDK docs. Once the SDK is installed, initialize it.
Radar.initialize(publishableKey: "prj_live_pk_...")
Android (Kotlin)
Radar.initialize(context, "prj_live_pk_...")

Set the user

When a professional logs in, set their user ID and any relevant metadata. This links all location updates and events to the professional in your system. iOS (Swift)
Radar.setUserId("pro_user_456")
Radar.setMetadata(["companyId": "company_abc123", "name": "John Doe"])
Android (Kotlin)
Radar.setUserId("pro_user_456")
Radar.setMetadata(JSONObject().apply {
    put("companyId", "company_abc123")
    put("name", "John Doe")
})
Setting metadata, like companyId, makes it easy to filter the data by company or team on the server side.

Request location permissions

This experience requires background location permissions so the SDK can track the professional’s location when the app is not in the foreground. Following these docs to request background permissions for iOS and Android.

Steps

1

Create a geofence for the shift location

When a shift is created, create a geofence for its location. This will be used to detect when the professional arrives and departs.Use the Geofences API to create a geofence server-side when a shift is created. Link the geofence to the shift using an externalId so you can reference it. Setting deleteAfter automatically deletes the geofence after the shift window, keeping your project clean without requiring a manual delete call. 
curl https://api.radar.io/v1/geofences/shift/shift_12345 \
  -H "Authorization: prj_live_sk_..." \
  -X PUT \
  -d "description=123 Main St - Water Heater Repair" \
  -d "type=circle" \
  -d "coordinates=[-73.9911,40.7342]" \
  -d "radius=100" \
  -d "metadata[shiftId]=shift_12345" \
  -d "metadata[customerId]=customer_abc" \
  -d "deleteAfter=2026-01-01T00:00:00.000Z"
2

Start a trip when the professional begins traveling

When the professional taps “Start route,” start a Radar trip. Trips provide real-time ETA tracking, delay detection, and approaching signals - everything needed to power a live tracking view for a dispatcher or customer.iOS (Swift)
let tripMetadata: [String: String] = [
    "shiftId": "shift_12345",
    "proName": "John Doe"
]

let tripOptions = RadarTripOptions(
    externalId: "trip_shift_12345",
    destinationGeofenceTag: "shift",
    destinationGeofenceExternalId: "shift_12345"
)
tripOptions.mode = .car
tripOptions.metadata = tripMetadata

Radar.startTrip(options: tripOptions) { status, trip, events in
    if status == .success {
        // trip started, begin showing live ETA to dispatcher and customer
    } else {
        // handle error
    }
}
Android (Kotlin)
val tripMetadata = JSONObject()
tripMetadata.put("shiftId", "shift_12345")
tripMetadata.put("proName", "John Doe")

val tripOptions = RadarTripOptions(
    "trip_shift_12345",
    tripMetadata,
    "shift",
    "shift_12345",
    Radar.RadarRouteMode.CAR
)

Radar.startTrip(tripOptions) { status, trip, events ->
    if (status == Radar.RadarStatus.SUCCESS) {
        // trip started, begin showing live ETA to dispatcher and customer
    } else {
        // handle error
    }
}
Setting scheduledArrivalAt enables Radar’s delay detection: if the professional’s ETA exceeds the scheduled arrival time, Radar will automatically surface a delay signal. We recommend implementing silent push notifications alongside trip tracking to ensure location updates continue even if the professional manually terminates the app.Tracking frequencyRadar’s SDK supports custom tracking options as well as three tracking presets: EFFICIENT, RESPONSIVE, and CONTINUOUS. You can remotely set tracking options in the Radar dashboard, overriding any client-side specified tracking options.In addition to general tracking options, Radar supports overriding tracking behavior during a trip or inside a geofence with a specified tag.As a professional is on their way to a shift, we recommend:
  • On-trip tracking options: increase tracking frequency while a professional is actively on a trip using a modified version of theCONTINUOUS preset, with desiredStoppedUpdateInterval: 60 and desiredMovingUpdateInterval: 60.
  • In-geofence tracking options: increase tracking frequency to every 30s via the base CONTINUOUS preset when a professional is inside a shift geofence to ensure accurate clock-in/out detection while on-site.
3

Handle trip signals

Trips stream real-time locations and ETA updates to your backend via webhooks. Use these events to power a dispatcher dashboard and/or customer-facing tracking view.Key trip events to listen for:
EventDescription
user.started_tripProfessional is on their way
user.updated_tripLocation or ETA updated
user.approaching_trip_destinationProfessional is within the approaching threshold
user.arrived_at_trip_destinationProfessional has arrived at the destination
user.stopped_tripTrip completed or canceled
Offline behavior: If the professional loses connection during a trip, the SDK will buffer location updates and replay them once connectivity is restored. Your backend will receive the replayed locations in order, so no location data should be lost.No-show detection: As the professional travels, eta.duration is updated on each user.updated_trip event. If the ETA continues to increase over time rather than decrease, this may indicate the professional is moving away from the shift. You can implement custom no-show logic on your backend by tracking ETA trends across successive events and marking a shift as a no-show after a configurable threshold.Example user.updated_trip payload:
{
  "events": [
    {
      "type": "user.updated_trip",
      "createdAt": "2026-05-12T14:30:00.000Z",
      "live": true,
      "trip": {
        "externalId": "trip_shift_12345",
        "status": "started",
        "metadata": {
          "shiftId": "shift_12345",
          "proName": "John Doe"
        },
        "mode": "car",
        "destinationGeofenceTag": "shift",
        "destinationGeofenceExternalId": "shift_12345",
        "eta": {
          "duration": 8,
          "distance": 3200
        }
      }
    }
  ],
  "user": {
    "userId": "pro_user_456"
  }
}
4

Clock-in on geofence entry

When the professional enters the shift geofence, Radar triggers a user.entered_geofence event. Use createdAt from the webhook payload at the clock-in timestamp.
{
  "type": "user.entered_geofence",
  "createdAt": "2026-05-12T14:32:00.000Z",
  "live": true,
  "location": {
    "type": "Point",
    "coordinates": [-73.9911, 40.7342]
  },
  "geofence": {
    "tag": "shift",
    "externalId": "shift_12345",
    "metadata": {
      "shiftId": "shift_12345"
    }
  },
  "user": {
    "userId": "pro_user_456",
    "metadata": {
      "companyId": "company_abc123",
      "name": "John Doe"
    }
  }
}
Update the shift record in your system with the clock-in timestamp.
5

Clock-out on geofence exit

When the professional exits the shift geofence, Radar triggers a user.exited_geofence event. Use createdAt as the clock-out timestamp and calculate the time onsite.
{
  "type": "user.exited_geofence",
  "createdAt": "2026-05-12T16:15:00.000Z",
  "live": true,
  "location": {
    "type": "Point",
    "coordinates": [-73.9911, 40.7342]
  },
  "geofence": {
    "tag": "shift",
    "externalId": "shift_12345",
    "metadata": {
      "shiftId": "shift_12345"
    }
  },
  "user": {
    "userId": "pro_user_456",
    "metadata": {
      "companyId": "company_abc123",
      "name": "John Doe"
    }
  }
}
Time onsite = exitedAt - enteredAt
6

Calculate mileage for reimbursement

When the trip completes, pass it to Radar’s route matching API to calculate the distance traveled for mileage reimbursement.Request:
curl "https://api.radar.io/v1/route/match" \
  -H "Authorization: prj_live_pk_..." \
  -H "Content-Type: application/json" \
  -X POST \
  -d '{
    "path": [
      {"coordinates": "40.7128,-74.0060", "createdAt": "2026-05-12T14:00:00Z", "accuracy": 10},
      {"coordinates": "40.7200,-73.9980", "createdAt": "2026-05-12T14:05:00Z", "accuracy": 10},
      {"coordinates": "40.7342,-73.9911", "createdAt": "2026-05-12T14:12:00Z", "accuracy": 10}
    ],
    "mode": "car",
    "units": "imperial"
  }'
Response:
{
  "meta": {
    "code": 200
  },
  "matchedPath": [
    {"latitude": 40.7128, "longitude": -74.0060, "originalIndex": 0},
    {"latitude": 40.7200, "longitude": -73.9980, "originalIndex": 1},
    {"latitude": 40.7342, "longitude": -73.9911, "originalIndex": 2}
  ],
  "distance": {
    "value": 7439.5,
    "text": "1.4 mi"
  }
}
Use distance.text to display the trip mileage to the dispatcher and distance.value to calculate reimbursement.