SDKs & frameworks

Social Posting from Laravel

A working Laravel example that posts to social platforms via the PostGate API using the Http facade.

Quick answer

To post to social media from Laravel, call PostGate’s POST /api/posts directly with Laravel’s Http facade — no extra Composer dependency. Store the API key in .env and read it through config/services.php, send the body, profile IDs, media, and a stable Idempotency-Key, then confirm delivery from the targets array or webhooks rather than assuming the 202 means published.

PostGate's SDKs & MCP guide confirms that PostGate generates a typed PHP client from its OpenAPI contract, but the published Composer package name for it isn't documented publicly. This guide uses Laravel's own Http facade against POST /api/posts directly instead — no extra dependency, and guaranteed to match the documented REST contract.

01

Step 1 — Store the key outside version control

Add the key to .env (which Laravel keeps out of git by default) and read it through config/services.php rather than calling env() directly in application code. Use a pg_test_ key while you build; it runs the complete lifecycle against a sandbox adapter without publishing anything real. See Authentication.

config/services.phpPostGate API
// .env
POSTGATE_API_KEY=pg_test_...
POSTGATE_API_URL=https://postgate.studio

// config/services.php
return [
    // ...
    'postgate' => [
        'key' => env('POSTGATE_API_KEY'),
        'url' => env('POSTGATE_API_URL', 'https://postgate.studio'),
    ],
];
02

Step 2 — Send the request with the Http facade

Attach the connected profile ID, the post body, and a media URL, with a stable Idempotency-Key so a queued job retry never posts twice — see Idempotency & safe retries.

app/Services/PostGateClient.phpPostGate API
<?php

namespace App\Services;

use Illuminate\Support\Facades\Http;
use Illuminate\Support\Str;

class PostGateClient
{
    public function createPost(string $body, array $profileIds, array $media = [], ?string $idempotencyKey = null): array
    {
        $response = Http::withToken(config('services.postgate.key'))
            ->withHeaders([
                'Idempotency-Key' => $idempotencyKey ?? (string) Str::uuid(),
            ])
            ->post(config('services.postgate.url') . '/api/posts', [
                'post' => ['body' => $body],
                'profiles' => $profileIds,
                'media' => $media,
            ]);

        $response->throw();

        return $response->json();
    }
}
03

Step 3 — Call it from a controller or job

Because publishing is asynchronous, dispatch it from a queued job when the caller shouldn't wait on delivery — a job ID also gives you a natural, stable value to derive the Idempotency-Key from, so a failed job that Laravel retries never double-posts.

app/Http/Controllers/PostController.phpPostGate API
public function store(Request $request, PostGateClient $postgate)
{
    $validated = $request->validate([
        'body' => 'required|string|max:3000',
        'profile_id' => 'required|string',
    ]);

    $post = $postgate->createPost(
        body: $validated['body'],
        profileIds: [$validated['profile_id']],
        idempotencyKey: "web-{$request->user()->id}-" . now()->timestamp,
    );

    return response()->json($post, 202);
}
04

Step 4 — Confirm delivery, do not assume it

The create response is acceptance, not proof of publication. Read GET /api/posts/{id} and inspect the targets array — a published target carries the platform's own post ID and permalink. For production traffic, subscribe to the post.published / post.partial / post.failed webhooks and update your ownposts table instead of polling — see the full delivery lifecycle.

05

Step 5 — Handle the failure paths you will actually hit

A failed target returns a structured error with a stable code and a retryable flag — see errors & retries. Catch Illuminate\\Http\\Client\\RequestException from throw(), read $exception->response->json('error'), and only queue a retry when retryable is true — reusing the original Idempotency-Key, not a new one.

Frequently asked questions

Is there an official PostGate Composer package for Laravel?

PostGate generates a typed PHP client from its OpenAPI contract, but the published Composer package name for it isn’t documented publicly. Using Laravel’s own Http facade against POST /api/posts directly adds no extra dependency and is guaranteed to match the documented REST contract.

How should I store the PostGate API key in a Laravel app?

Add it to .env (which Laravel keeps out of git by default) and read it through config/services.php rather than calling env() directly in application code. Use a pg_test_ key while you build — it runs the complete lifecycle against a sandbox adapter without publishing anything real.

How do I stop a retried Laravel queue job from double-posting?

Send a stable Idempotency-Key with the request, derived from your own job ID rather than a fresh UUID per attempt. When handling a failed target, only queue a retry when the structured error’s retryable flag is true — and reuse the original Idempotency-Key, not a new one.

How do I know the post actually published from my Laravel app?

The create response is acceptance, not proof of publication. Read GET /api/posts/{id} and inspect the targets array — a published target carries the platform’s own post ID and permalink. For production traffic, subscribe to the post.published / post.partial / post.failed webhooks and update your own posts table instead of polling.

Build it with PostGate

One API for every social platform your customers care about — sandboxed, documented, and ready today.

Start building Read the docs