VideohatiDocs
SDKs

Laravel SDK

Official videohati/laravel SDK — install, configure, upload video, create playback sessions, verify webhooks, and embed the player from a Laravel app.

videohati/laravel is the official PHP SDK for Laravel. The service provider and Videohati facade auto-register, TLS certificate verification is always on, and exceptions never contain your key. PHP 8.2+, Laravel 10 or 11. Version 1.0.0-rc.0.

Install

composer require videohati/laravel

Set credentials in .env:

VIDEOHATI_API_KEY=vh_live_...
VIDEOHATI_PROJECT_ID=01JZ9WV3N8GQ5T2M7K4C6XBARF

A vh_test_... key runs against test data; the mode always follows the key prefix.

Configure

Publish the config file to change defaults:

php artisan vendor:publish --tag=videohati-config

config/videohati.php reads these environment variables:

VariableDefaultWhat it sets
VIDEOHATI_API_KEYProject API key.
VIDEOHATI_BASE_URLhttps://api.videohati.comAPI origin.
VIDEOHATI_PROJECT_IDDefault project for the @videohatiPlayer directive.
VIDEOHATI_WEBHOOK_SECRETEndpoint secret for SignatureVerifier.
VIDEOHATI_PLAYER_URLhttps://cdn.videohati.com/player.jsPlayer bundle URL for the directive.
VIDEOHATI_TIMEOUT30HTTP timeout in seconds.

Facade and dependency injection

Reach the client through the Videohati facade or by injecting Videohati\Laravel\Client.

use Videohati\Laravel\Facades\Videohati;

$videos = Videohati::videos()->list(state: 'ready');
use Videohati\Laravel\Client;

class LessonController
{
    public function __construct(private readonly Client $videohati) {}

    public function show(string $videoId)
    {
        $video = $this->videohati->videos()->get($videoId);
        // ...
    }
}

The client exposes videos(), playback(), webhookEndpoints(), and webhooks() (the signature verifier).

Videos

videos()->list() returns a Laravel Collection<VideoDto>.

MethodSignatureWhat it does
listlist(?string $state, int $limit = 20, ?string $cursor)One page as Collection<VideoDto>.
listPagelistPage(?string $state, int $limit = 20, ?string $cursor)The raw page array, including nextCursor.
getget(string $videoId)One VideoDto.
createcreate(string $originalFilename)Registers a video; returns its id. Step 1.
deletedelete(string $videoId)Soft-deletes; aborts any active upload.
startUploadstartUpload(string $videoId, int $sizeBytes, string $contentType, ?string $checksumSha256, ?string $idempotencyKey)Opens a multipart upload; returns an UploadStartDto. Step 2.
completeUploadcompleteUpload(string $videoId, ?string $checksumSha256)Finalizes the upload. Step 3.
abortUploadabortUpload(string $videoId)Cancels an open upload.
uploadupload(string $filePath, ?string $contentType, ?string $idempotencyKey, ?callable $onProgress)Runs the full flow in one call.
use Videohati\Laravel\Facades\Videohati;

$videos = Videohati::videos()->list(state: 'ready', limit: 20);
$videos->each(fn ($video) => logger($video->id . ' ' . $video->state));

$video = Videohati::videos()->get($videoId); // VideoDto
Videohati::videos()->delete($videoId);       // soft delete

upload($filePath) runs POST /v1/videosPOST .../upload/start → sequential part uploads (3 attempts each; 5xx/429 retried, 4xx fail fast) → POST .../upload/complete. The progress callback receives bytes uploaded and the total.

$result = Videohati::videos()->upload(
    storage_path('app/lecture-01.mp4'),
    onProgress: fn (int $uploaded, int $total) => logger("{$uploaded}/{$total}"),
);

VideoDto carries id, state, originalFilename, sizeBytes, contentType, checksumSha256, errorCode, errorMessage, readyAt, failedAt, createdAt, updatedAt, and — on detail responses — upload and encoding. UploadStartDto adds partUrl($partNumber) to build each part's upload URL.

Create a playback session

Never ship an API key to a browser. Create the session server-side and emit only the session token into the page.

playback()->createSession() returns a PlaybackSessionDto with sessionId, manifestUrl, sessionToken, watermarkToken, expiresAt, heartbeatIntervalSeconds, and showFreeBadge.

$session = Videohati::playback()->createSession(
    videoId: $video->id,
    viewerDisplayText: $user->name,
);

The full signature is createSession(string $videoId, ?int $ttlSeconds, ?array $referrerAllowlist, ?string $viewerIdFromRequest, ?string $viewerDisplayText).

Embed the player

The @videohatiPlayer Blade directive emits the embed snippet — the player <script> plus the data-videohati-* div.

{{-- resources/views/lesson.blade.php --}}
@videohatiPlayer(['videoId' => $video->id, 'sessionToken' => $session->sessionToken])

projectId defaults to VIDEOHATI_PROJECT_ID. Optional keys: projectId, autoplay ('muted' or true), width, height, lang ('ar' / 'en'), viewerText, apiOrigin, playerUrl. See the Player page for the attributes and events.

Webhook endpoints

webhookEndpoints() manages endpoints and reads deliveries. Each method returns the decoded JSON body as an array.

MethodSignatureWhat it does
registerregister(string $projectId, array $params)Registers an endpoint. secret is returned once.
listlist(string $projectId)Lists endpoints.
getget(string $projectId, string $id)One endpoint.
updateupdate(string $projectId, string $id, array $params)Updates an endpoint.
deletedelete(string $projectId, string $id)Soft-deletes an endpoint.
rotateSecretrotateSecret(string $projectId, string $id)Mints a fresh secret (returned once).
reopenreopen(string $projectId, string $id)Clears a tripped circuit breaker.
sendTestsendTest(string $projectId, string $id)Enqueues a synthetic video.encoding.ready delivery.
listDeliverieslistDeliveries(string $projectId, string $id, array $query = [])One page of delivery attempts.

Verify webhooks

webhooks() returns a SignatureVerifier. Pass the raw request body — not re-serialized JSON. Verify in a controller or route, or wrap it in middleware.

use Illuminate\Http\Request;
use Videohati\Laravel\Webhooks\SignatureVerifier;

Route::post('/webhooks/videohati', function (Request $request) {
    $event = SignatureVerifier::verifyAndParse(
        $request->getContent(), // the raw body
        $request->header(SignatureVerifier::HEADER, ''),
        config('videohati.webhook_secret'),
    );

    if ($event === null) {
        abort(400); // invalid signature
    }

    if ($event['type'] === 'video.encoding.ready') {
        // React to the ready video.
    }

    return response()->noContent(); // 204
});

SignatureVerifier::verify(...) returns a bool when you only need the check; verifyAndParse(...) returns the decoded envelope array or null. The header is Videohati-Signature: t=<unix_ts>,v1=<hex_sha256> (HMAC-SHA256 over <unix_ts>.<raw_body>); comparison is constant-time and timestamps more than 300 seconds old are rejected.

Errors

Every non-2xx response throws a typed subclass of Videohati\Laravel\Exceptions\VideohatiException, which carries status and apiCode (the API's machine code, for example video_not_found).

ClassStatusWhen
ConnectionExceptionThe request never reached the API (DNS, TLS, socket, or timeout).
ValidationException400The body or query failed validation.
AuthenticationException401Missing or invalid credential.
PermissionException403Valid credential, insufficient permission.
NotFoundException404Resource missing or not visible.
ConflictException409Conflicts with the resource's current state.
RateLimitException429Rate limit exceeded; read retryAfterSeconds.
ServerException5xxThe API failed to complete the request.
use Videohati\Laravel\Exceptions\NotFoundException;
use Videohati\Laravel\Exceptions\RateLimitException;
use Videohati\Laravel\Exceptions\VideohatiException;

try {
    $video = Videohati::videos()->get($videoId);
} catch (NotFoundException) {
    abort(404);
} catch (RateLimitException $e) {
    sleep($e->retryAfterSeconds ?? 1);
} catch (VideohatiException $e) {
    report($e); // $e->status, $e->apiCode, $e->getMessage()
}