Skip to main content
Technology & EngineeringMaps Geolocation Services314 lines

Radar

"Radar: geofencing, geocoding, trip tracking, address validation, maps, fraud detection, JavaScript SDK"

Quick Summary15 lines
Radar is a location infrastructure platform that treats geolocation as a first-class backend concern rather than a purely client-side rendering problem. While traditional mapping libraries focus on visual display, Radar provides server-side primitives -- geofences, trip tracking, address validation, IP geocoding, and fraud detection -- that power location-aware business logic. The platform exposes both a client-side JavaScript SDK for browser and mobile contexts and a server-side REST API for backend workflows. Use Radar when your product needs to answer "where is this user and what does that mean for our business" rather than just "show a map." Radar's Maps product provides a cost-effective alternative to Google Maps for display purposes, but its real differentiator is the geofencing and intelligence layer.

## Key Points

- **Use publishable keys client-side, secret keys server-side**: Publishable keys are safe for browsers but have limited scope. Never expose secret keys in frontend bundles.
- **Set userId before tracking**: All geofence events, trip updates, and analytics are associated with the userId. Set it immediately after authentication.
- **Use webhook integrations for real-time events**: Configure Radar webhooks to push geofence entry/exit events to your backend rather than polling the API.
- **Batch geocoding server-side**: For bulk address validation (e.g., importing a CSV of customer addresses), use the server API with rate limiting rather than client-side calls.
- **Combine IP geocoding with device GPS**: Use IP geocoding as a coarse fallback and fraud signal. It is not accurate enough for geofencing or navigation.
- **Tag geofences by category**: Use the `tag` field (e.g., "store", "warehouse", "delivery-zone") to organize geofences and filter events by business domain.
- **Monitor trip ETAs via webhooks**: Subscribe to `trip.updated` webhook events to push real-time ETA updates to your customers without polling.
- **Ignoring geofence event deduplication**: Radar can fire the same geofence entry event if the user's location jitters at a boundary. Deduplicate events by `event._id` in your webhook handler.
- **Creating geofences client-side**: The client SDK cannot create geofences. Always create and manage geofences through the server API or the Radar dashboard.
skilldb get maps-geolocation-services-skills/RadarFull skill: 314 lines
Paste into your CLAUDE.md or agent config

Radar

Core Philosophy

Radar is a location infrastructure platform that treats geolocation as a first-class backend concern rather than a purely client-side rendering problem. While traditional mapping libraries focus on visual display, Radar provides server-side primitives -- geofences, trip tracking, address validation, IP geocoding, and fraud detection -- that power location-aware business logic. The platform exposes both a client-side JavaScript SDK for browser and mobile contexts and a server-side REST API for backend workflows. Use Radar when your product needs to answer "where is this user and what does that mean for our business" rather than just "show a map." Radar's Maps product provides a cost-effective alternative to Google Maps for display purposes, but its real differentiator is the geofencing and intelligence layer.

Setup

Install the SDK and initialize:

// npm install radar-sdk-js
import Radar from "radar-sdk-js";
import "radar-sdk-js/dist/radar.css"; // only needed if using Radar Maps

// Initialize with your publishable key
Radar.initialize(process.env.NEXT_PUBLIC_RADAR_PUBLISHABLE_KEY!);

For server-side calls, use the REST API directly:

const RADAR_SECRET_KEY = process.env.RADAR_SECRET_KEY!;

async function radarApi<T>(
  endpoint: string,
  params: Record<string, string> = {}
): Promise<T> {
  const url = new URL(`https://api.radar.io/v1${endpoint}`);
  Object.entries(params).forEach(([k, v]) => url.searchParams.set(k, v));

  const res = await fetch(url, {
    headers: { Authorization: RADAR_SECRET_KEY },
  });

  if (!res.ok) {
    const error = await res.json();
    throw new Error(`Radar API error: ${error.meta?.message ?? res.statusText}`);
  }

  return res.json() as Promise<T>;
}

Key Techniques

Forward and Reverse Geocoding

interface RadarAddress {
  formattedAddress: string;
  latitude: number;
  longitude: number;
  country: string;
  state: string;
  city: string;
  postalCode: string;
  confidence: "exact" | "interpolated" | "fallback";
}

// Client-side forward geocode
async function geocodeAddress(query: string): Promise<RadarAddress[]> {
  const result = await Radar.forwardGeocode({ query });
  return result.addresses;
}

// Server-side reverse geocode
async function reverseGeocode(lat: number, lng: number): Promise<RadarAddress> {
  const data = await radarApi<{ addresses: RadarAddress[] }>("/geocode/reverse", {
    coordinates: `${lat},${lng}`,
  });
  return data.addresses[0];
}

Address Validation and Autocomplete

// Autocomplete for search inputs
async function autocompleteAddress(
  query: string,
  near?: { latitude: number; longitude: number }
): Promise<RadarAddress[]> {
  const result = await Radar.autocomplete({
    query,
    near: near ? near : undefined,
    limit: 5,
    layers: ["address", "place"],
  });
  return result.addresses;
}

// Server-side address validation
interface ValidationResult {
  address: RadarAddress;
  verificationStatus: "verified" | "unverified" | "ambiguous";
  result: {
    deliverability: "deliverable" | "undeliverable" | "unknown";
  };
}

async function validateAddress(addressString: string): Promise<ValidationResult> {
  const data = await radarApi<{ result: ValidationResult }>("/addresses/validate", {
    address: addressString,
  });
  return data.result;
}

Geofencing

// Create a geofence via the server API
interface GeofenceParams {
  tag: string;
  externalId: string;
  description: string;
  type: "circle" | "polygon";
  coordinates: [number, number]; // [lng, lat] for circle center
  radius: number; // meters, for circle type
  metadata?: Record<string, string>;
}

async function createGeofence(params: GeofenceParams): Promise<void> {
  await fetch("https://api.radar.io/v1/geofences", {
    method: "POST",
    headers: {
      Authorization: RADAR_SECRET_KEY,
      "Content-Type": "application/json",
    },
    body: JSON.stringify({
      tag: params.tag,
      externalId: params.externalId,
      description: params.description,
      type: params.type,
      coordinates: params.coordinates,
      radius: params.radius,
      metadata: params.metadata,
    }),
  });
}

// Client-side: track user and detect geofence events
async function startGeofenceTracking(): Promise<void> {
  // Identify the user first
  Radar.setUserId("user-123");
  Radar.setMetadata({ plan: "premium" });

  // Single location check
  const { events, location, user } = await Radar.trackOnce();

  // events contains geofence entries/exits
  events.forEach((event) => {
    if (event.type === "user.entered_geofence") {
      console.log(`Entered: ${event.geofence?.description}`);
    }
    if (event.type === "user.exited_geofence") {
      console.log(`Exited: ${event.geofence?.description}`);
    }
  });
}

Trip Tracking

interface TripOptions {
  externalId: string;
  destinationGeofenceTag: string;
  destinationGeofenceExternalId: string;
  mode: "car" | "foot" | "bike";
}

// Start tracking a trip (e.g., delivery driver en route)
async function startTrip(options: TripOptions): Promise<void> {
  Radar.setUserId("driver-456");

  await Radar.startTrip({
    tripOptions: {
      externalId: options.externalId,
      destinationGeofenceTag: options.destinationGeofenceTag,
      destinationGeofenceExternalId: options.destinationGeofenceExternalId,
      mode: options.mode,
    },
    trackingOptions: {
      desiredStoppedUpdateInterval: 30,
      desiredMovingUpdateInterval: 15,
      desiredSyncInterval: 10,
      desiredAccuracy: "high",
      stopDuration: 60,
      stopDistance: 50,
      sync: "all",
      replay: "stops",
      showBlueBar: true,
    },
  });
}

// Query trip status server-side
interface TripStatus {
  externalId: string;
  status: "started" | "approaching" | "arrived" | "completed" | "canceled";
  eta: { duration: number; distance: number };
}

async function getTripStatus(tripExternalId: string): Promise<TripStatus> {
  const data = await radarApi<{ trip: TripStatus }>(`/trips/${tripExternalId}`);
  return data.trip;
}

// Complete or cancel
async function completeTrip(): Promise<void> {
  await Radar.completeTrip();
  Radar.stopTracking();
}

Fraud Detection with IP Geocoding

interface IpGeocodeResult {
  ip: string;
  city: string;
  region: string;
  country: string;
  latitude: number;
  longitude: number;
  proxy: boolean;
  vpn: boolean;
  tor: boolean;
  datacenter: boolean;
}

async function checkIpFraudSignals(ip: string): Promise<{
  location: IpGeocodeResult;
  riskFlags: string[];
}> {
  const data = await radarApi<{ address: IpGeocodeResult }>("/geocode/ip", { ip });
  const location = data.address;

  const riskFlags: string[] = [];
  if (location.proxy) riskFlags.push("proxy_detected");
  if (location.vpn) riskFlags.push("vpn_detected");
  if (location.tor) riskFlags.push("tor_exit_node");
  if (location.datacenter) riskFlags.push("datacenter_ip");

  return { location, riskFlags };
}

Radar Maps (Display)

import Radar from "radar-sdk-js";

function initRadarMap(containerId: string) {
  const map = Radar.ui.map({
    container: containerId,
    style: "radar-default-v1",
    center: [-73.9857, 40.7484],
    zoom: 12,
  });

  // Add a marker
  Radar.ui.marker({ text: "HQ" })
    .setLngLat([-73.9857, 40.7484])
    .addTo(map);

  // The map instance is Maplibre GL JS under the hood
  map.on("load", () => {
    map.addSource("deliveries", {
      type: "geojson",
      data: { type: "FeatureCollection", features: [] },
    });

    map.addLayer({
      id: "delivery-points",
      type: "circle",
      source: "deliveries",
      paint: {
        "circle-radius": 6,
        "circle-color": "#6366f1",
      },
    });
  });

  return map;
}

Best Practices

  • Use publishable keys client-side, secret keys server-side: Publishable keys are safe for browsers but have limited scope. Never expose secret keys in frontend bundles.
  • Set userId before tracking: All geofence events, trip updates, and analytics are associated with the userId. Set it immediately after authentication.
  • Use webhook integrations for real-time events: Configure Radar webhooks to push geofence entry/exit events to your backend rather than polling the API.
  • Batch geocoding server-side: For bulk address validation (e.g., importing a CSV of customer addresses), use the server API with rate limiting rather than client-side calls.
  • Combine IP geocoding with device GPS: Use IP geocoding as a coarse fallback and fraud signal. It is not accurate enough for geofencing or navigation.
  • Tag geofences by category: Use the tag field (e.g., "store", "warehouse", "delivery-zone") to organize geofences and filter events by business domain.
  • Monitor trip ETAs via webhooks: Subscribe to trip.updated webhook events to push real-time ETA updates to your customers without polling.

Anti-Patterns

  • Using Radar solely as a map renderer: Radar Maps is built on MapLibre GL and is cost-effective, but the platform's value is in geofencing, tracking, and intelligence. If you only need map display, a free tile provider with Leaflet or MapLibre may be simpler.
  • Polling trackOnce in a tight loop: Radar.trackOnce() is for single location checks. For continuous tracking, use Radar.startTrip() or Radar.trackContinuous() which handle battery-efficient background updates.
  • Ignoring geofence event deduplication: Radar can fire the same geofence entry event if the user's location jitters at a boundary. Deduplicate events by event._id in your webhook handler.
  • Creating geofences client-side: The client SDK cannot create geofences. Always create and manage geofences through the server API or the Radar dashboard.
  • Validating addresses without handling ambiguous results: The validation API returns ambiguous when multiple matches exist. Always present disambiguation options to the user rather than silently picking the first result.
  • Skipping metadata on users and geofences: Metadata fields let you attach business context (plan tier, store ID, driver shift) that flows through to webhook payloads, making event processing much easier downstream.

Install this skill directly: skilldb add maps-geolocation-services-skills

Get CLI access →