Symfony
The FoPost PHP client as a Symfony bundle, with autowiring, console commands, and webhook events.
fopost/symfony-bundle is a thin wrapper over the PHP SDK. Every request, retry, model, and error type lives there; the bundle wires it into Symfony: one configured client for autowiring, two console commands, and a webhook endpoint that turns deliveries into Symfony events.
Needs PHP 8.1 or newer and Symfony 6.4 LTS or 7.x.
composer require fopost/symfony-bundleThis is a 0.x release. The surface is still settling and a minor version may break something. Pin an exact version if that matters to you.
Published on Packagist. Source and issues: github.com/fopost/fopost-symfony. MIT licensed.
Configuration
With Symfony Flex the bundle registers itself. Without it, add it to config/bundles.php:
return [
// ...
Fopost\Symfony\FopostBundle::class => ['all' => true],
];config/packages/fopost.yaml:
fopost:
api_key: '%env(FOPOST_API_KEY)%'
base_url: '%env(FOPOST_API_URL)%'
timeout: '%env(float:FOPOST_API_TIMEOUT)%'
max_retries: '%env(int:FOPOST_API_MAX_RETRIES)%'
# Optional: an empty fallback leaves them null.
default_workspace_id: '%env(default::FOPOST_WORKSPACE_ID)%'
webhook_secret: '%env(default::FOPOST_WEBHOOK_SECRET)%'.env:
FOPOST_API_KEY=fp_your_key_here
FOPOST_API_URL=https://api.fopost.com
FOPOST_API_TIMEOUT=30
FOPOST_API_MAX_RETRIES=3Every key takes a literal too, so api_key: 'fp_...' works while you are trying things out. Only api_key is required.
| Key | Default | What it does |
|---|---|---|
api_key | none, required | Your API key. Sent as X-API-Key |
base_url | the hosted API | API root. A host with no path gets the versioned path appended |
timeout | 30.0 | Seconds to wait for one request |
max_retries | 3 | Attempts a rate limited request gets |
default_workspace_id | null | Workspace the console commands use when --workspace is left off |
webhook_secret | null | Secret shown once when you created the webhook. Without it every delivery is rejected |
Quick start
The bundle registers Fopost\Sdk\Client for autowiring, so type hint it anywhere:
use Fopost\Sdk\Client;
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
use Symfony\Component\HttpFoundation\JsonResponse;
use Symfony\Component\Routing\Attribute\Route;
final class PublishController extends AbstractController
{
#[Route('/publish', methods: ['POST'])]
public function __invoke(Client $fopost): JsonResponse
{
$workspace = $fopost->workspaces()->list()[0];
$accounts = $fopost->accounts()->list($workspace->id);
$post = $fopost->posts()->create(
workspaceId: $workspace->id,
content: 'Shipping today: scheduled posting straight from Symfony.',
accounts: $accounts,
status: 'scheduled',
scheduleAt: new \DateTimeImmutable('+1 hour'),
);
return new JsonResponse(['id' => $post->id]);
}
}$fopost->posts(), accounts(), workspaces(), labels(), ai(), and the request() escape hatch are the full surface, documented on the PHP SDK page. The bundle adds nothing to it.
Console commands
# List the accounts connected to a workspace
php bin/console fopost:accounts --workspace ws_123
# Create a draft
php bin/console fopost:post "Shipping today" -a acc_1 -a acc_2
# Schedule it
php bin/console fopost:post "Shipping today" -a acc_1 --schedule-at 2026-09-01T09:00:00Z
# Send it now
php bin/console fopost:post "Shipping today" -a acc_1 --publishBoth commands fall back to default_workspace_id when --workspace is left off.
Receiving webhooks
Import the route the bundle ships. config/routes/fopost.yaml:
fopost:
resource: '@FopostBundle/config/routes/webhooks.yaml'That mounts POST /fopost/webhook. Create a webhook pointing at it and copy the secret it shows once into FOPOST_WEBHOOK_SECRET. The controller verifies the HMAC-SHA256 signature over the raw request body, answers 401 when it does not match, and dispatches a Symfony event when it does.
use Fopost\Symfony\Event\FopostPostPublishedEvent;
use Symfony\Component\EventDispatcher\Attribute\AsEventListener;
final class PostPublishedListener
{
#[AsEventListener]
public function __invoke(FopostPostPublishedEvent $event): void
{
// $event->event 'post.published'
// $event->data the event body
// $event->timestamp when the API sent it
// $event->deliveryId the X-FoPost-Delivery header
}
}One class per event, all extending FopostWebhookEvent:
| Event | Class |
|---|---|
post.published | FopostPostPublishedEvent |
post.failed | FopostPostFailedEvent |
post.partially_failed | FopostPostPartiallyFailedEvent |
delivery.published | FopostDeliveryPublishedEvent |
delivery.failed | FopostDeliveryFailedEvent |
delivery.delayed | FopostDeliveryDelayedEvent |
account.health_changed | FopostAccountHealthChangedEvent |
Every delivery is dispatched twice (once under its own class, once under FopostWebhookEvent), so listening on the base class catches everything, including an event this bundle does not know yet. Listeners run inside the request, so keep them quick or hand the work to Messenger.
Errors
Every failure is a Fopost\Sdk\Exception\FopostException subclass, so one catch covers the lot:
use Fopost\Sdk\Exception\FopostException;
use Fopost\Sdk\Exception\RateLimitException;
use Fopost\Sdk\Exception\ValidationException;
try {
$fopost->posts()->publish($postId);
} catch (ValidationException $e) {
// 400 and 422
} catch (RateLimitException $e) {
// retry after $e->retryAfter seconds
} catch (FopostException $e) {
// everything else
}Rate limited requests are retried for you, honouring Retry-After.
Testing your app
Swap the transport and nothing touches the network:
use Fopost\Sdk\Http\Transport;
// In config/services_test.yaml, or a compiler pass:
$container->getDefinition('fopost.client')->setArgument('$transport', new Reference(MyFakeTransport::class));Next
Related documentation
- SDKs & Integrations
Official FoPost clients for TypeScript, Python, PHP, Ruby, Go, Rust, Java, .NET, Swift, Kotlin, Dart, and Elixir, framework integrations from Laravel to Next.js, and tooling for the CLI, CI, Terraform, and the automation platforms.
- SDKs Overview
Every official FoPost client, what it covers, and how to pick one.
- TypeScript SDK
The official TypeScript and Node.js client for the FoPost API.
- Python SDK
The official Python client for the FoPost API.
- PHP SDK
The official PHP client for the FoPost API, with no framework and no HTTP library.
- Ruby SDK
The official Ruby client for the FoPost API, with no runtime dependencies.
- Go SDK
The official Go client for the FoPost API.
- Rust SDK
The official Rust client for the FoPost API, async and built on reqwest.