↗

Provisioning 1,000+ routers with TR-069 from a Laravel app

Every internet provider has the same quiet problem: thousands of routers and modems sitting in customers’ homes, each needing configuration, updates and the occasional fix. Doing that by hand, or over the phone with the customer, doesn’t scale.

TR-069 is the standard answer. On ISPKenya, a multi-tenant platform for 10+ ISPs, I integrated GenieACS so providers could provision and manage 1,000+ customer devices from a Laravel dashboard, cutting manual device operations by about 60%. This post explains how TR-069 fits together with a Laravel application, and the patterns I would use again.

TR-069 in two minutes

TR-069 (officially CWMP) defines how customer devices, called CPE, talk to a management server, called an ACS.

Where GenieACS fits

You could implement CWMP yourself, but you shouldn’t. GenieACS is an open-source ACS that handles the protocol, stores device state in MongoDB, and exposes a REST API, the NBI (northbound interface), for everything else. That split is the key architectural decision:

The Laravel app never talks CWMP. It asks GenieACS to do things through the NBI and reads device state back.

A thin client around the NBI

Keep every NBI call in one service class. It is the only place that knows URLs, encoding rules and timeouts.

class GenieAcs
{
    public function __construct(private string $baseUrl) {}

    public function findDevice(string $serial): ?array
    {
        $query = json_encode(['_deviceId._SerialNumber' => $serial]);

        return Http::baseUrl($this->baseUrl)
            ->get('/devices/', ['query' => $query])
            ->throw()
            ->json(0);
    }

    public function setParameters(string $deviceId, array $values): bool
    {
        $response = Http::baseUrl($this->baseUrl)
            ->withQueryParameters(['connection_request' => '', 'timeout' => 3000])
            ->post('/devices/'.rawurlencode($deviceId).'/tasks', [
                'name' => 'setParameterValues',
                'parameterValues' => $values, // [[path, value, type], ...]
            ])
            ->throw();

        // 200: the device applied it now. 202: queued for its next session.
        return $response->status() === 200;
    }
}

Two details that save debugging time. GenieACS device IDs contain characters that must be URL-encoded, so always rawurlencode() them. And the task endpoint can answer 200 (executed immediately after a successful connection request) or 202 (queued until the device next checks in). Treat 202 as a normal outcome, not a failure, and show it in the UI as “pending”.

Provisioning as a lifecycle, not a button

“Provision this router” is really a sequence: find the device, link it to the right tenant and customer, apply that customer’s settings, confirm the device reported back, and record what happened. On ISPKenya the platform covered the whole lifecycle, provision, update and delete, including automatic port allocation, so staff no longer stitched those steps together by hand.

In Laravel, that sequence belongs in queued jobs:

Let GenieACS handle the routine

Not everything needs to go through Laravel. GenieACS presets and provisions run scripts automatically when devices inform, which suits defaults every device should get, like the inform interval or a management password. Keep Laravel for anything tied to business data, such as a customer’s Wi-Fi name or their plan, and let presets handle what is the same for every device.

Audit every device action

When staff from several ISPs can change real hardware in customers’ homes, every action needs a trail: who did it, for which tenant, to which device, what changed, and whether the device confirmed it. That log is what you reach for when a customer calls to say their internet stopped working, and on ISPKenya it sat alongside role-based access across 3 roles, so only the right people could act at all.

What I would tell a team starting out

Done this way, device management stops being a support burden and becomes part of the product. The ISPKenya case study covers the rest of the platform, including the multi-tenant architecture behind it.

Let's talk about yours.

Book a free 30-minute call. Within 48 hours you'll have a written plan with scope, timeline and a fixed price.