DEV Community

Cover image for SMM Panel API Integration: Connecting Is Easy, Keeping Orders in Sync Is Harder
Wismook Teams
Wismook Teams

Posted on AI-assisted

SMM Panel API Integration: Connecting Is Easy, Keeping Orders in Sync Is Harder

At ZO Panel, we used to handle a situation that made this difference obvious. An order would be marked completed. The customer would dispute it, and after checking, we would find that it should have been canceled instead.
At ZO Panel, we used to handle a situation that made this difference obvious. An order would be marked completed. The customer would dispute it, and after checking, we would find that it should have been canceled instead.

The provider had to correct the order manually. Then we had to make the matching correction on our panel.

One customer order. Two separate updates.

Our current Perfect Panel connections handle that correction differently: when the order status changes on the connected side, the change can also reach our panel after the order has already been marked completed. We see this from both directions, whether we are buying from a provider or supplying services to a reseller.

That experience changed what I pay attention to when looking at an integration. A successful connection is a starting point. It does not tell you how the system will behave when an earlier status turns out to be wrong.

Disclosure: I run ZO Panel. The operational example above comes from our own workflow. The PHP example below is illustrative and has not been tested against a live account.

When both panels use Perfect Panel

In our workflow, a reseller using Perfect Panel can take the API key from their account profile on our panel and enter it in their own panel’s provider settings.

Perfect Panel’s provider connection guide explains why this is straightforward: providers using the same platform are already integrated, so the connection does not require writing a new API client.

There is still setup work. Connecting a provider and deciding which services to offer are separate decisions. Service selection, pricing, and synchronization settings deserve a check before customers start placing orders.

I would also avoid describing this as “everything syncs.” That phrase hides the details a reseller actually needs to understand.

Which service fields update? Which order changes reach the receiving panel? What happens to an order that was completed yesterday and corrected today?

Our experience with corrections after completion is useful, but it should not be treated as a promise about every installation. Perfect Panel’s public feature list also describes a manual option for requesting updated statuses of mistakenly completed orders. Verify the behavior of your own connection.

When the other system does not use Perfect Panel

For a custom application or another panel platform, start with the provider’s documented request format.

The ZO Panel API documentation specifies a POST endpoint at https://zosmm.com/api/v2, JSON responses, and actions for retrieving services, submitting orders, checking statuses, and checking balance.

An API key alone does not make an unrelated application compatible. The receiving software needs to support those requests and interpret the responses, either through an existing connector or code you write.

For a first connection check, retrieve the service list. There is no need to create a paid order just to check whether your application can communicate with the endpoint.

A small PHP request example

This example requires PHP 8 or later with cURL enabled. Store the key in the server’s environment as ZOSMM_API_KEY, rather than putting it in a public file or browser script.

<?php

function panelRequest(string $action, array $params = []): array
{
    $key = getenv('ZOSMM_API_KEY');

    if (!$key) {
        throw new RuntimeException('Missing API key.');
    }

    $ch = curl_init('https://zosmm.com/api/v2');

    curl_setopt_array($ch, [
        CURLOPT_POST => true,
        CURLOPT_POSTFIELDS => http_build_query(
            array_merge($params, ['key' => $key, 'action' => $action])
        ),
        CURLOPT_HTTPHEADER => [
            'Content-Type: application/x-www-form-urlencoded',
        ],
        CURLOPT_RETURNTRANSFER => true,
        CURLOPT_CONNECTTIMEOUT => 10,
        CURLOPT_TIMEOUT => 30,
    ]);

    $body = curl_exec($ch);
    $httpCode = curl_getinfo($ch, CURLINFO_HTTP_CODE);
    $failed = $body === false;

    curl_close($ch);

    if ($failed) {
        throw new RuntimeException('Transport failure; outcome unknown.');
    }

    if ($httpCode < 200 || $httpCode >= 300) {
        throw new RuntimeException("Unexpected HTTP status: {$httpCode}");
    }

    $data = json_decode($body, true, 512, JSON_THROW_ON_ERROR);

    if (!is_array($data)) {
        throw new RuntimeException('Unexpected response structure.');
    }

    if (array_key_exists('error', $data)) {
        throw new RuntimeException('API returned an error; review securely.');
    }

    return $data;
}

$services = panelRequest('services');

printf("Received %d services.\n", count($services));
Enter fullscreen mode Exit fullscreen mode

The timeouts are example client settings, not documented provider limits. This function handles a single request; it does not implement storage, scheduling, or order reconciliation.

Notice that it checks both the HTTP response and the decoded body. Those are different layers. Your application should not proceed simply because it received a response.

Keep your order ID and the provider’s order ID separate

When submitting an order, your application already has a local record. The provider returns its own order identifier.

Keep both.

Use the local identifier for your customer-facing order and the provider identifier for later status requests. Store the relationship before sending the order into any background processing workflow.

There is another detail worth handling before you automate submissions: a timeout leaves uncertainty.

Your application may stop waiting after the provider has accepted the request. Automatically submitting the same order again could create a duplicate. A timeout should therefore move the order into a review or reconciliation path, rather than trigger an unconditional retry.

The documentation reviewed for this example does not establish an idempotency mechanism. Do not assume repeated submissions are harmless.

A completed order may still need reconciliation

The correction we used to make manually exposes a weakness in a common design: stop checking an order forever as soon as it becomes completed.

That design is simple. It also means the application has no way to discover a later correction unless another process brings it back into view.

For a custom integration, distinguish the current order status from your policy for checking it again.

Active orders need routine updates. Disputed orders need a way to request a fresh status. Recently closed orders may need a separate reconciliation policy, depending on what the provider supports.

That does not mean polling every historical order indefinitely. It means deciding how corrections will reach your system instead of assuming they never happen.

Keep a history of corrections

Overwriting a status tells you what the order says now. Keeping a status history tells you what changed.

A useful record includes the previous status, the new status, the provider order ID, and when your application observed the change. Keep credentials out of that record.

Financial handling needs its own care. A status correction should not cause the same customer balance adjustment to run twice. Updating the displayed status and recording a refund are related operations, but they are not interchangeable.

Before relying on the connection, ask one concrete question: if the provider corrects an order after your system has closed it, how will that correction reach the customer’s order record?

Top comments (0)