Developer Integration Guide

How to connect WordPress plugins, PHP apps, Laravel projects, or Android apps to this update server.

Your API endpoint:

https://wordpress.refat.ovh/api/update.php

How It Works

This is a self-hosted update server. Instead of distributing plugins through WordPress.org or another marketplace, your apps call this server's API to check for updates and download new versions. The flow is:

  1. Your plugin/app calls the API with its current version and slug.
  2. The server compares that version against the latest stored version.
  3. If a newer version exists, the server returns metadata and a download URL.
  4. Your plugin/app downloads the ZIP and installs it โ€” or (for WordPress) hands it to WordPress core to handle.

Plugin slug rules: lowercase letters, numbers, and hyphens only (e.g. my-cool-plugin). For WordPress plugins, this must exactly match the plugin folder name and main file name.

WordPress Plugin Integration

1. Create the updater class

Save this as class-plugin-updater.php inside your plugin folder.

<?php
if ( ! defined( 'ABSPATH' ) ) exit;

class My_Plugin_Updater {
    private $api_url = '', $plugin_file = '', $plugin_slug = '', $current_version = '';

    public function __construct( $api_url, $plugin_file ) {
        $this->api_url         = trailingslashit( $api_url );
        $this->plugin_file     = $plugin_file;
        $this->plugin_slug     = basename( dirname( $plugin_file ) );
        $this->current_version = get_plugin_data( $plugin_file )['Version'];

        add_filter( 'pre_set_site_transient_update_plugins', [ $this, 'check_for_update' ] );
        add_filter( 'plugins_api', [ $this, 'plugin_api_call' ], 10, 3 );
        add_action( 'upgrader_process_complete', [ $this, 'clear_update_transient' ], 10, 2 );
    }

    public function check_for_update( $transient ) {
        if ( empty( $transient->checked ) ) return $transient;
        $basename = plugin_basename( $this->plugin_file );
        $response = $this->api_request( 'update_check', [ 'slug' => $this->plugin_slug, 'version' => $this->current_version ] );
        if ( $response ) {
            $key = version_compare( $response->new_version, $this->current_version, '>' ) ? 'response' : 'no_update';
            $transient->$key[ $basename ] = $response;
        }
        return $transient;
    }

    public function plugin_api_call( $result, $action, $args ) {
        if ( 'plugin_information' !== $action || ! isset( $args->slug ) || $args->slug !== $this->plugin_slug ) return $result;
        return $this->api_request( 'plugin_information', [ 'slug' => $this->plugin_slug ] ) ?: $result;
    }

    private function api_request( $action, $args ) {
        $response = wp_remote_post( $this->api_url, [
            'body'    => [ 'action' => $action, 'request' => wp_json_encode( $args ) ],
            'timeout' => 15,
        ]);
        if ( is_wp_error( $response ) || 200 !== wp_remote_retrieve_response_code( $response ) ) return false;
        $body = json_decode( wp_remote_retrieve_body( $response ), true );
        return ( $body && ! empty( $body['success'] ) && ! empty( $body['data'] ) ) ? (object) $body['data'] : false;
    }

    public function clear_update_transient( $upgrader, $options ) {
        if ( isset( $options['action'], $options['type'], $options['plugins'] )
             && 'update' === $options['action'] && 'plugin' === $options['type']
             && in_array( plugin_basename( $this->plugin_file ), $options['plugins'], true )
        ) delete_site_transient( 'update_plugins' );
    }
}

2. Initialise in your main plugin file

<?php
/**
 * Plugin Name: Your Plugin Name
 * Version:     1.0.0
 * Requires PHP: 7.4
 */

require_once __DIR__ . '/class-plugin-updater.php';

function my_plugin_start_updater() {
    new My_Plugin_Updater( 'https://wordpress.refat.ovh/api/update.php', __FILE__ );
}
add_action( 'admin_init', 'my_plugin_start_updater' );

Testing tip: WordPress caches update checks for ~12 hours via its update_plugins transient. Force an immediate check via Dashboard โ†’ Updates โ†’ Check Again. The recommended updater class does not add its own cache on top of this โ€” updates appear as soon as WordPress runs its next check.

3. Plugin metadata fields (info.json reference)

When you add a plugin via the admin panel, the server stores this JSON structure. Here's what each field does in the WordPress update dialog:

FieldUsed by WordPress forFormat
namePlugin name in update listPlain text
versionLatest version to compare againstSemver string e.g. 1.2.0
requiresMinimum WordPress version warninge.g. 5.8
requires_phpMinimum PHP version warninge.g. 7.4
tested"Tested up to" in the plugin popupe.g. 6.5
sections.descriptionDescription tab in plugin popupPlain text (HTML stripped)
sections.changelogChangelog tab in plugin popupHTML โ€” h4, ul, li, strong, em, a allowed
banners.lowBanner image in plugin popup (772ร—250)Image URL or uploaded path
banners.highRetina banner (1544ร—500)Image URL or uploaded path
icons.1xPlugin icon in the list (128ร—128)Image URL or uploaded path
icons.2xRetina plugin icon (256ร—256)Image URL or uploaded path

PHP / Laravel Integration

Generic PHP

<?php
define('MY_SLUG',    'my-custom-app');
define('MY_VERSION', '1.0.0');
$api_url = 'https://wordpress.refat.ovh/api/update.php';

$ch = curl_init();
curl_setopt_array($ch, [
    CURLOPT_URL            => $api_url,
    CURLOPT_POST           => true,
    CURLOPT_POSTFIELDS     => http_build_query([
        'action'  => 'update_check',
        'request' => json_encode(['slug' => MY_SLUG, 'version' => MY_VERSION]),
    ]),
    CURLOPT_RETURNTRANSFER => true,
    CURLOPT_TIMEOUT        => 15,
    CURLOPT_SSL_VERIFYPEER => true,
]);
$result   = curl_exec($ch);
curl_close($ch);

$response = json_decode($result);
if ($response && $response->success && isset($response->data)) {
    $data = $response->data;
    if (version_compare($data->new_version, MY_VERSION, '>')) {
        echo "Update available: v" . $data->new_version . "\n";
        echo "Download: " . $data->package . "\n";
    }
}

Laravel Artisan Command

<?php
namespace App\Console\Commands;
use Illuminate\Console\Command;
use Illuminate\Support\Facades\Http;

class CheckProjectUpdate extends Command {
    protected $signature = 'project:check-update';

    public function handle(): int {
        $response = Http::timeout(15)->post('https://wordpress.refat.ovh/api/update.php', [
            'action'  => 'update_check',
            'request' => json_encode(['slug' => 'my-laravel-app', 'version' => '1.0.0']),
        ]);
        $data = $response->json('data');
        if ($data && version_compare($data['new_version'], '1.0.0', '>')) {
            $this->info("New version: " . $data['new_version']);
            $this->line("Download:    " . $data['package']);
            if (!empty($data['checksum'])) $this->line("Checksum:    " . $data['checksum']);
        } else {
            $this->info('Already up to date.');
        }
        return Command::SUCCESS;
    }
}

Android (Kotlin) Integration

Add <uses-permission android:name="android.permission.INTERNET" /> to your manifest, then:

import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.withContext
import org.json.JSONObject
import java.io.OutputStreamWriter
import java.net.HttpURLConnection
import java.net.URL

object UpdateChecker {
    private const val API_URL = "https://wordpress.refat.ovh/api/update.php"
    private const val SLUG    = "my-android-app"
    private const val VERSION = "1.0.0"

    suspend fun checkForUpdate() = withContext(Dispatchers.IO) {
        try {
            val payload = "action=update_check&request=" +
                java.net.URLEncoder.encode(
                    JSONObject().put("slug", SLUG).put("version", VERSION).toString(), "UTF-8"
                )
            val conn = (URL(API_URL).openConnection() as HttpURLConnection).apply {
                requestMethod = "POST"; connectTimeout = 15000; readTimeout = 15000
                doOutput = true; setRequestProperty("Content-Type", "application/x-www-form-urlencoded")
            }
            OutputStreamWriter(conn.outputStream).use { it.write(payload) }
            if (conn.responseCode != 200) return@withContext null
            val json = JSONObject(conn.inputStream.bufferedReader().readText())
            if (!json.optBoolean("success")) return@withContext null
            val data = json.getJSONObject("data")
            Pair(data.optString("new_version"), data.optString("package"))
        } catch (e: Exception) { null }
    }
}

Releasing a New Version

  1. Bump the version number in your plugin header or version constant.
  2. Build your release ZIP โ€” it should contain the plugin folder at its root (e.g. my-plugin/my-plugin.php).
  3. In the admin panel: go to your plugin โ†’ Add New Version โ†’ enter the version number โ†’ upload the ZIP.
  4. The server automatically updates info.json to reflect the new latest version.
  5. Clients polling the API will receive the update on their next check.

Version comparison: The server uses PHP's version_compare(), which follows standard semver rules. 1.10.0 is correctly treated as greater than 1.9.0.

API Reference v1

All POST requests go to:

https://wordpress.refat.ovh/api/update.php

All responses are JSON and include "api_version": "v1". CORS is enabled (Access-Control-Allow-Origin: *).
Rate limits: 30 API calls / IP / minute  ยท  10 downloads / IP / minute

POST update_check

Checks whether a newer version exists. Call this on application startup or periodically.

POST fieldValue
actionupdate_check
requestJSON string: {"slug":"your-slug","version":"1.0.0"}

Response โ€” always returns the latest version data:

{
  "success": true,
  "data": {
    "slug": "your-slug",
    "new_version": "1.2.0",
    "package": "https://your-server.com/api/update.php?action=download_plugin&slug=your-slug&version=1.2.0",
    "checksum": "sha256:a3f5c8...",
    "url": "https://your-server.com/plugin/your-slug",
    "tested": "6.7",
    "requires_php": "7.4"
  }
}

The server always returns the full data object with the latest version. Version comparison is the client's responsibility โ€” compare data.new_version against your installed version using version_compare() to decide whether an update is available.

Response when the plugin slug is not found on the server:

{ "success": true, "data": null }

POST plugin_information

Returns full plugin metadata โ€” used by WordPress for the plugin details popup.

POST fieldValue
actionplugin_information
requestJSON string: {"slug":"your-slug"}

GET download_plugin

Streams the ZIP file for a specific version directly to the client.

GET https://wordpress.refat.ovh/api/update.php?action=download_plugin&slug=your-slug&version=1.2.0

GET ping

Health check โ€” see the Status Endpoint section below.

Error HTTP codes

CodeMeaning
400Bad request โ€” missing or invalid parameters
403Forbidden โ€” path traversal detected
404Plugin or version not found
405Method not allowed
429Rate limit exceeded โ€” back off and retry
500Server error

Checksum Verification

Every update_check response includes a checksum field (sha256:<hex>). Always verify this before installing a downloaded package to ensure the file wasn't tampered with in transit.

// PHP example
$file     = '/tmp/plugin-update.zip';
file_put_contents($file, file_get_contents($data->package));

$expected = str_replace('sha256:', '', $data->checksum);
$actual   = hash_file('sha256', $file);

if (!hash_equals($expected, $actual)) {
    throw new RuntimeException('Checksum mismatch โ€” aborting install.');
}

$zip = new ZipArchive();
$zip->open($file);
$zip->extractTo('/path/to/install/');
$zip->close();

Rollback / Specific Version Downloads

Every version with a source/ folder on the server is individually downloadable. Pass the exact version string to the download endpoint:

GET https://wordpress.refat.ovh/api/update.php?action=download_plugin&slug=your-slug&version=1.0.0

The plugin detail page on this server lists all available versions with individual download buttons.

Health / Status Endpoint

GET https://wordpress.refat.ovh/api/update.php?action=ping

Returns HTTP 200 and a JSON body when the server is healthy:

{
  "success": true,
  "status": "ok",
  "api_version": "v1",
  "php_version": "8.2.0",
  "plugins": 4,
  "server_time": "2025-10-15T12:00:00+00:00"
}

Compatible with UptimeRobot, Better Uptime, and any HTTP health-check service. The full visual status dashboard is available at https://wordpress.refat.ovh/api/status.