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/laravelSet credentials in .env:
VIDEOHATI_API_KEY=vh_live_...
VIDEOHATI_PROJECT_ID=01JZ9WV3N8GQ5T2M7K4C6XBARFA 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-configconfig/videohati.php reads these environment variables:
| Variable | Default | What it sets |
|---|---|---|
VIDEOHATI_API_KEY | — | Project API key. |
VIDEOHATI_BASE_URL | https://api.videohati.com | API origin. |
VIDEOHATI_PROJECT_ID | — | Default project for the @videohatiPlayer directive. |
VIDEOHATI_WEBHOOK_SECRET | — | Endpoint secret for SignatureVerifier. |
VIDEOHATI_PLAYER_URL | https://cdn.videohati.com/player.js | Player bundle URL for the directive. |
VIDEOHATI_TIMEOUT | 30 | HTTP 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>.
| Method | Signature | What it does |
|---|---|---|
list | list(?string $state, int $limit = 20, ?string $cursor) | One page as Collection<VideoDto>. |
listPage | listPage(?string $state, int $limit = 20, ?string $cursor) | The raw page array, including nextCursor. |
get | get(string $videoId) | One VideoDto. |
create | create(string $originalFilename) | Registers a video; returns its id. Step 1. |
delete | delete(string $videoId) | Soft-deletes; aborts any active upload. |
startUpload | startUpload(string $videoId, int $sizeBytes, string $contentType, ?string $checksumSha256, ?string $idempotencyKey) | Opens a multipart upload; returns an UploadStartDto. Step 2. |
completeUpload | completeUpload(string $videoId, ?string $checksumSha256) | Finalizes the upload. Step 3. |
abortUpload | abortUpload(string $videoId) | Cancels an open upload. |
upload | upload(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 deleteupload($filePath) runs POST /v1/videos → POST .../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.
| Method | Signature | What it does |
|---|---|---|
register | register(string $projectId, array $params) | Registers an endpoint. secret is returned once. |
list | list(string $projectId) | Lists endpoints. |
get | get(string $projectId, string $id) | One endpoint. |
update | update(string $projectId, string $id, array $params) | Updates an endpoint. |
delete | delete(string $projectId, string $id) | Soft-deletes an endpoint. |
rotateSecret | rotateSecret(string $projectId, string $id) | Mints a fresh secret (returned once). |
reopen | reopen(string $projectId, string $id) | Clears a tripped circuit breaker. |
sendTest | sendTest(string $projectId, string $id) | Enqueues a synthetic video.encoding.ready delivery. |
listDeliveries | listDeliveries(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).
| Class | Status | When |
|---|---|---|
ConnectionException | — | The request never reached the API (DNS, TLS, socket, or timeout). |
ValidationException | 400 | The body or query failed validation. |
AuthenticationException | 401 | Missing or invalid credential. |
PermissionException | 403 | Valid credential, insufficient permission. |
NotFoundException | 404 | Resource missing or not visible. |
ConflictException | 409 | Conflicts with the resource's current state. |
RateLimitException | 429 | Rate limit exceeded; read retryAfterSeconds. |
ServerException | 5xx | The 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()
}React SDK
Official @videohati/react SDK — the VideohatiPlayer component plus upload, metadata, and webhook-delivery hooks for React 18+ and Next.js.
Player
Official @videohati/player — the framework-free web player. Embed it with a script tag or install it from npm, then drive it with a small imperative API.