Social Posting from Laravel
A working Laravel example that posts to social platforms via the PostGate API using the Http facade.
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.
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.
// .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'),
],
];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.
<?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();
}
}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.
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);
}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.
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.
