Skip to main content
Fleetalyse
Partner API & Tracking API

Sample retail-order integration

A complete small PHP integration from payment to cancellation.

The Fleetalyse Partner Programme is running as a pilot. The API documented here is available to approved partners; you can build against test mode as soon as your partner account is open. About partnering with Fleetalyse

A complete, small PHP integration for a shop that sells a tracker with a monthly tracking subscription. It shows the whole journey: your payment provider confirms the sale → find or create the customer and fleet by your own references → register the device → request the activation (safe to retry) → verify the Fleetalyse webhook → cancel when your customer cancels.

Your retail payment and the Fleetalyse service are separate: a paid retail order does not bypass Fleetalyse credit checks, and cancelling your customer's subscription does not stop the Fleetalyse service until you call the deactivation endpoint.

fleetalyse.php — a tiny client

fleetalyse.php
<?php
declare(strict_types=1);

// Minimal Fleetalyse Partner API client. The key comes from your server's secret store.
final class Fleetalyse
{
    public function __construct(private string $key, private string $base = 'https://fleetalyse.co.uk/partner-api/v1') {}

    public function get(string $path, array $query = []): array
    {
        return $this->call('GET', $path . ($query ? '?' . http_build_query($query) : ''));
    }

    public function post(string $path, array $body, string $idempotencyKey): array
    {
        return $this->call('POST', $path, $body, $idempotencyKey);
    }

    private function call(string $method, string $path, ?array $body = null, ?string $idem = null): array
    {
        for ($attempt = 1; ; $attempt++) {
            $ch = curl_init($this->base . $path);
            $headers = ['Authorization: Bearer ' . $this->key, 'Accept: application/json'];
            if ($body !== null) {
                $headers[] = 'Content-Type: application/json';
                $headers[] = 'Idempotency-Key: ' . $idem;          // same key on every retry
                curl_setopt($ch, CURLOPT_POSTFIELDS, json_encode($body));
            }
            curl_setopt_array($ch, [CURLOPT_CUSTOMREQUEST => $method, CURLOPT_HTTPHEADER => $headers,
                CURLOPT_RETURNTRANSFER => true, CURLOPT_TIMEOUT => 30, CURLOPT_HEADER => true]);
            $raw = curl_exec($ch);
            $status = (int)curl_getinfo($ch, CURLINFO_RESPONSE_CODE);
            $headerSize = (int)curl_getinfo($ch, CURLINFO_HEADER_SIZE);
            curl_close($ch);
            $json = $raw === false ? null : json_decode(substr($raw, $headerSize), true);
            $retryable = $raw === false || $status === 429 || $status >= 500 || ($json['error']['retryable'] ?? false);
            if ($status >= 200 && $status < 300) {
                return $json ?? [];
            }
            if (!$retryable || $attempt >= 5) {
                throw new RuntimeException(($json['error']['code'] ?? 'HTTP_' . $status) . ' (' . ($json['error']['request_id'] ?? 'no request id') . ')');
            }
            $retryAfter = preg_match('/^Retry-After:\s*(\d+)/mi', (string)$raw, $m) ? (int)$m[1] : 0;
            sleep(max($retryAfter, 2 ** $attempt) + random_int(0, 2));
        }
    }
}

order-paid.php — after your payment provider confirms payment

order-paid.php
<?php
declare(strict_types=1);
require __DIR__ . '/fleetalyse.php';

// Called by YOUR payment provider's verified webhook once the retail order is paid.
// $order comes from your database: id, customer id/name, tracker IMEI, plan, lines …
function on_order_paid(array $order): void
{
    $api = new Fleetalyse(getenv('FLEETALYSE_KEY'));

    // 1. Find or create the Fleetalyse customer by YOUR customer id.
    $found = $api->get('/customers', ['external_reference' => 'C' . $order['customer_id']]);
    $customer = $found['data'][0] ?? $api->post('/customers', [
        'name' => $order['customer_company'],
        'external_reference' => 'C' . $order['customer_id'],
    ], 'customer-C' . $order['customer_id']);

    // 2. Find or create the customer's fleet for this plan.
    $fleetRef = 'C' . $order['customer_id'] . '-' . $order['plan'];
    $found = $api->get('/fleets', ['external_reference' => $fleetRef]);
    $fleet = $found['data'][0] ?? $api->post('/fleets', [
        'customer_id' => $customer['id'],
        'name' => $order['customer_company'] . ' (' . $order['plan'] . ')',
        'plan_code' => $order['plan'],
        'external_reference' => $fleetRef,
    ], 'fleet-' . $fleetRef);

    foreach ($order['lines'] as $line) {
        // 3. Register the tracker (or find it if it is already registered).
        $found = $api->get('/devices', ['identifier' => $line['imei']]);
        $device = $found['data'][0] ?? $api->post('/devices', [
            'identifier_type' => 'imei',
            'identifier' => $line['imei'],
            'manufacturer' => $line['manufacturer'],     // as listed in Supported devices
            'model' => $line['model'],
            'fleet_id' => $fleet['id'],
            'label' => $line['vehicle_label'],
            'registration_plate' => $line['registration'],
        ], 'device-' . $line['imei']);

        // 4. Request the activation: 202 + operation. Safe to retry with the same keys.
        $operation = $api->post('/activations', [
            'external_order_reference' => 'ORDER-' . $order['id'],
            'external_request_id' => 'ORDER-' . $order['id'] . '-line-' . $line['id'] . '-activate',
            'customer_id' => $customer['id'],
            'fleet_id' => $fleet['id'],
            'device_id' => $device['id'],
            'activate_when' => 'as_soon_as_ready',
        ], 'order-' . $order['id'] . '-line-' . $line['id'] . '-activation');

        save_line_status($line['id'], 'activation_requested', $operation['operation_id'], $operation['service_id']);
    }
}

fleetalyse-webhook.php — completion and failures

fleetalyse-webhook.php
<?php
declare(strict_types=1);
// Point your webhook endpoint here (subscribed to service.* and operation.* events).

$raw = file_get_contents('php://input');
if (!fleetalyse_verify($raw, $_SERVER['HTTP_FLEETALYSE_SIGNATURE'] ?? '', getenv('FLEETALYSE_WEBHOOK_SECRET'))) {
    http_response_code(400);                    // see the verification example on the Webhooks page
    exit;
}
$event = json_decode($raw, true);

// At-least-once delivery: process each event id once (UNIQUE index on event_id).
if (!insert_ignore_processed_event($event['id'])) {
    http_response_code(200);
    exit;
}
http_response_code(204);
if (function_exists('fastcgi_finish_request')) {
    fastcgi_finish_request();                   // answer within 10 s, then work
}

$data = $event['data'];
switch ($event['type']) {
    case 'service.activation_completed':
        // Charging started at effective_at; show the tracker as active to your customer.
        mark_lines_active($data['external_order_reference'], $data['device_id'], $data['service_id'], $data['effective_at']);
        break;
    case 'service.activation_failed':
        mark_line_failed($data['external_order_reference'], $data['device_id']);
        notify_ops('Activation failed for ' . $data['device_id']);
        break;
    case 'service.deactivation_completed':
        // Charging stopped at effective_at.
        mark_service_stopped($data['service_id'], $data['effective_at']);
        break;
    case 'operation.requires_attention':
        notify_ops('Fleetalyse operation needs attention: ' . $event['resource']['id']);
        break;
}

cancel.php — your customer cancels

cancel.php
<?php
declare(strict_types=1);
require __DIR__ . '/fleetalyse.php';

// Your customer cancelled their retail subscription: stop the Fleetalyse service too.
function on_subscription_cancelled(array $line): void
{
    $api = new Fleetalyse(getenv('FLEETALYSE_KEY'));
    $operation = $api->post('/services/' . $line['fleetalyse_service_id'] . '/deactivations', [
        'effective' => 'immediate',
    ], 'line-' . $line['id'] . '-deactivate');

    // 202: requested. Charging stops when service.deactivation_completed arrives.
    save_line_status($line['id'], 'deactivation_requested', $operation['operation_id'], $line['fleetalyse_service_id']);
}

Why it is safe to retry

  • Every write uses an Idempotency-Key derived from your order line, so a retry after a timeout returns the original result.
  • Customers and fleets are found by external_reference before creating them.
  • The activation carries external_request_id, which stays unique permanently (idempotency keys expire after 30 days).
  • The webhook handler ignores event ids it has already processed and only moves the order forward.
WhatsApp us