Skip to content

feat: Zoleo satellite communicator integration (push-through proxy) #79

@techtimo

Description

@techtimo

Context

Zoleo is a satellite communicator (similar to SPOT) whose developer API is push-based: Zoleo pushes GPS events to a registered server URL. Each developer holds a single set of API credentials; end users link their ZOLEO Account ID to the developer app via Zoleo's ZConnect consent flow. This means direct plugin integration (user pastes their own API key) is not possible — Zoleo's architecture requires a centralized receiver.

Rather than buffering data in a proxy database, the chosen architecture is a push-through proxy: Zoleo pushes to the proxy, the proxy immediately forwards to the WordPress site's existing ingest endpoint, and the data lands directly in wp_spotmap_points. The proxy only needs a routing table (ZOLEO Account ID → WordPress URL + shared secret), not a full DB.


Architecture

Zoleo servers
    │  POST Location Share+, Check-In, SOS events
    ▼
Spotmap Proxy  (separate service, hosted by plugin developer)
    │  routing table:  zoleo_account_id → { wordpress_url, shared_secret }
    │  immediately forwards each event
    ▼
WordPress site  POST /wp-json/spotmap/v1/ingest/zoleo?key=<shared_secret>
    │
    ▼
wp_spotmap_points  (existing table, no schema change needed)

No cron job is needed on the WordPress side — data arrives via webhook only.


Part 1: WordPress Plugin Changes

1.1 New feed type — includes/class-spotmap-providers.php

Add 'zoleo' to the Spotmap_Providers::all() static array alongside the existing providers (findmespot, osmand, teltonika, etc.).

'zoleo' => [
    'label'  => 'Zoleo Satellite Communicator',
    'fields' => [
        [ 'key' => 'name',            'type' => 'text',     'label' => 'Feed Name',         'required' => true ],
        [ 'key' => 'zoleo_account_id','type' => 'text',     'label' => 'ZOLEO Account ID',  'required' => true,
          'description' => 'Your 7+ character ZOLEO Account ID. Used to route data from the Spotmap proxy.' ],
        [ 'key' => 'ingest_key',      'type' => 'password', 'label' => 'Ingest Secret Key', 'required' => true,
          'description' => 'Shared secret between this site and the Spotmap proxy. Generate a random string.' ],
    ],
    'push' => true,   // no cron needed
],

1.2 Ingest endpoint — includes/class-spotmap-ingest.php

Register a new REST route following the existing OsmAnd/Teltonika pattern (includes/class-spotmap-ingest.php:26-48):

register_rest_route( Spotmap_Rest_Api::NAMESPACE, '/ingest/zoleo', [
    'methods'             => WP_REST_Server::CREATABLE,   // POST
    'callback'            => [ __CLASS__, 'handle_zoleo' ],
    'permission_callback' => '__return_true',
] );

Implement handle_zoleo( WP_REST_Request $request ):

  1. Extract key from request params; find feed via self::find_feed_by_key('zoleo', $key) (same helper used by OsmAnd handler).
  2. Bail with 401 if key not found, 403 if feed is paused.
  3. Parse the Zoleo push payload (JSON body). Expected field mapping — confirm exact names against a real Zoleo test push before finalising:
Zoleo field (tentative) wp_spotmap_points column Notes
latitude latitude decimal degrees
longitude longitude decimal degrees
altitude altitude metres
speed speed km/h
timestamp (Unix) time
batteryPercentage battery_status store as string, e.g. "85%"
message message user-typed text if present
deviceName device_name
  1. Map Zoleo event type → existing type values:
Zoleo event type value
Location Share+ TRACK
Check-In OK
SOS SOS
  1. Call ( new Spotmap_Database() )->insert_row( $data ).
  2. Return new WP_REST_Response( [ 'status' => 'ok' ], 200 ).

1.3 No cron, no API crawler changes

Because Zoleo is push-only, do not add a cron hook, do not modify class-spotmap-api-crawler.php, and do not add get_zoleo_feed_data() to class-spotmap-admin.php. The existing ensure_cron_scheduled() logic is untouched.

1.4 Admin UI copy

Add a help text block on the Zoleo feed form explaining:

  • User must link their ZOLEO account via the Spotmap proxy ZConnect URL (to be provided once proxy is live).
  • The "Ingest Secret Key" must match what is registered in the proxy.
  • The WordPress REST endpoint URL to register in the proxy: https://<site>/wp-json/spotmap/v1/ingest/zoleo?key=<ingest_key>

Part 2: Proxy Service (separate repository, not in this plugin)

Responsibilities

  • Register the Zoleo webhook URL with the Zoleo developer portal ("Setup Data Feed" screen, checked: Location Share+, Check-In, SOS).
  • Optionally enable Basic Auth on the Zoleo-facing endpoint.
  • Maintain a routing table: zoleo_account_id → { wordpress_url, ingest_key }.
  • On each incoming Zoleo push: look up the account, immediately POST the payload (or a normalised version) to {wordpress_url}/wp-json/spotmap/v1/ingest/zoleo?key={ingest_key}.
  • Respond 200 to Zoleo immediately (fire-and-forget forward; no retry/buffering needed for MVP).

Routing table storage

A simple key-value store is sufficient — no relational DB required. Options in order of simplicity:

  • Cloudflare Workers KV — zero-ops, free tier adequate for small user counts
  • SQLite file on a VPS
  • JSON file (single-user / self-hosted proxy only)

Self-service registration endpoint (MVP)

POST /register with body { zoleo_account_id, wordpress_url, ingest_key }.
For MVP this can be manually operated (you add entries). A self-service UI can follow once the basic flow is proven.

ZConnect flow

The proxy ZConnect Sign-up URL (configured in the Zoleo developer portal) is how end users consent to data sharing. After consent, Zoleo starts pushing that user's events. The proxy must have the routing entry before or at consent time.

Current ZConnect widget URL (production):

https://www.link.zoleo.com/?partnerID=9bb4cac8-82fe-42e1-bc9b-8b5ecc510daf&partnerURL=https%3A%2F%2Fgithub.com%2Ftechtimo

The partnerURL currently points to the GitHub profile. Once a self-service proxy registration page exists, update partnerURL to point to it so the consent and registration steps are a single flow for the user.

Suggested stack

Cloudflare Workers (free tier) + KV for routing table. Minimal deployment surface, no server to maintain, handles the required throughput easily.


User Setup Flow (end to end)

  1. User installs Spotmap and creates a new feed with type "Zoleo".
  2. They enter their ZOLEO Account ID and generate a random Ingest Secret Key.
  3. They register on the Spotmap proxy (initially: email/form to developer; later: self-service) providing their Account ID, WordPress URL, and Ingest Key.
  4. They visit the ZConnect URL from the Zoleo developer portal to consent to data sharing.
  5. Zoleo approves the account linkage (may take up to 1 business day per Zoleo docs).
  6. Zoleo begins pushing events → proxy routes → wp_spotmap_points → map renders.

Open Questions (resolve before implementation)

  1. Exact Zoleo push payload schema — field names and types. Trigger a test event from your dev account and inspect the raw POST body before finalising handle_zoleo().
  2. Zoleo Account ID format — is it the same identifier shown in the developer portal "ZOLEO Account IDs" table? Confirm it appears in push payloads for routing.
  3. Basic Auth on Zoleo-facing endpoint — the developer portal supports it; decide whether to use it for defence-in-depth on the proxy ingest URL.
  4. One-Way Command Messages — left unchecked in the portal screenshot. Confirm this is intentional (no GPS data in those events).
  5. Location Share+ subscription requirement — document clearly in plugin UI that end users need the Location Share+ add-on on their Zoleo plan.

Files to Modify in This Plugin

File Change
includes/class-spotmap-providers.php Add 'zoleo' provider entry
includes/class-spotmap-ingest.php Register /ingest/zoleo route + handle_zoleo() handler

No other plugin files need modification for the MVP push-through integration.


Verification

  1. Use curl -X POST (or ngrok + Zoleo test push) to hit /wp-json/spotmap/v1/ingest/zoleo with a sample payload and valid key — confirm row appears in wp_spotmap_points.
  2. Send with a wrong key — confirm 401 response.
  3. Pause the feed in admin — confirm 403 response.
  4. Confirm the new feed type appears in the Gutenberg block feed selector dropdown.
  5. End-to-end: trigger a real Zoleo Check-In, verify it flows proxy → WordPress → map marker.

Metadata

Metadata

Assignees

No one assigned

    Labels

    enhancementNew feature or request

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions