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

Essential cookies are always on. Optional cookies stay off unless you choose them.

Read our cookie policy