Zapier for business automation
In a corporate environment such as ORPI, automating business processes through APIs is essential to working more efficiently.
Zapier is a no-code automation platform that connects thousands of SaaS applications to one another. For a technical team, its value is twofold: business teams build their own automations, and developers only need to expose a few clean, secure entry points in their applications. This article shows how to prepare a Symfony application to talk to Zapier in both directions.
The basics
- Zap: an automation scenario, made of one trigger and one or more actions.
- Trigger: the event that starts the Zap, for example a new lead or a submitted form.
- Action: what the Zap does next, for example adding a row to a spreadsheet, sending a Slack message or calling your API.
- Task: every successfully executed action counts as a task, and billing is based on that volume.
A trigger works in one of two ways: polling, where Zapier regularly queries a URL to detect new items, or instant, where your application notifies Zapier through a webhook as soon as the event happens.
Common use cases
- Syncing a CRM to an internal database
- Slack notifications on business events
- Automatic report generation
- Integrating web forms with internal systems
What these cases have in common: simple, moderate-volume flows that connect existing tools. That is where Zapier pays off most, because it saves you from building and maintaining a connector for each tool.
Building a custom webhook
First direction: Zapier sends data to your application. In the Zap, the "Webhooks by Zapier" action in POST mode sends JSON to your endpoint, with headers you define, including a secret token:
// Symfony endpoint to receive Zapier webhooks
#[Route('/api/webhook/zapier', methods: ['POST'])]
class ZapierWebhookController extends AbstractController
{
public function __construct(
#[Autowire(env: 'ZAPIER_TOKEN')]
private readonly string $zapierToken,
) {}
public function __invoke(
Request $request,
MessageBusInterface $bus,
): JsonResponse {
// Validate the token
$token = (string) $request->headers->get('X-Zapier-Token', '');
if (!hash_equals($this->zapierToken, $token)) {
return $this->json(['error' => 'Unauthorized'], 401);
}
$data = json_decode($request->getContent(), true);
if (!is_array($data)) {
return $this->json(['error' => 'Invalid JSON'], 400);
}
// Dispatch the processing
$bus->dispatch(new ProcessZapierDataMessage($data));
return $this->json(['status' => 'received']);
}
}
Several details matter in this controller:
- the token is read from an environment variable through the
#[Autowire(env: ...)]attribute (Symfony 6.3 and later), never hard-coded; hash_equals()compares strings in constant time, which prevents the token from being guessed character by character;- the token is checked before the body is read at all, and invalid JSON is rejected with a 400 error;
- the actual processing goes to Messenger: the controller responds in a few milliseconds, which avoids timeouts on Zapier's side.
The secret is stored like the application's other secrets, for example with Symfony's secrets vault:
php bin/console secrets:set ZAPIER_TOKEN
# Generate a strong value
openssl rand -hex 32
An API for Zapier triggers
Second direction: your application provides data to Zapier. For a polling trigger, built in a private integration on Zapier's developer platform, all you need is an endpoint that returns the most recent items:
#[Route('/api/zapier/new-leads', methods: ['GET'])]
public function newLeads(LeadRepository $repo): JsonResponse
{
$leads = $repo->findRecent(limit: 50);
return $this->json(array_map(fn(Lead $l) => [
'id' => $l->getId(),
'name' => $l->getName(),
'email' => $l->getEmail(),
'created_at' => $l->getCreatedAt()->format('c'),
], $leads));
}
Zapier expects a JSON array of objects, newest first, each with a unique id field. It remembers the identifiers it has already seen and only triggers the Zap for new ones: that is deduplication. Two practical consequences: an item's id must never change, and findRecent() must sort by creation date, descending. Protect this endpoint too, for example with an API key sent in a header and checked by the Security component.
Instant triggers
Polling introduces a delay that depends on the Zapier plan. For an immediate reaction, the application can push the event to the URL provided by the "Catch Hook" trigger of Webhooks by Zapier. Do it from a Messenger handler, so that a Zapier outage never slows down your users:
#[AsMessageHandler]
final class NotifyZapierHandler
{
public function __construct(
private readonly HttpClientInterface $httpClient,
#[Autowire(env: 'ZAPIER_HOOK_URL')]
private readonly string $zapierHookUrl,
) {}
public function __invoke(LeadCreatedMessage $message): void
{
$response = $this->httpClient->request('POST', $this->zapierHookUrl, [
'json' => [
'id' => $message->leadId,
'name' => $message->name,
'email' => $message->email,
],
'timeout' => 10,
]);
// Throws an exception if the status is not 2xx: Messenger will retry
$response->getContent();
}
}
If Zapier is temporarily unavailable, the exception triggers Messenger's retry strategy, then the failure transport: no event is lost. For a public integration, Zapier also offers REST Hooks: Zapier registers its own URL with your API when a Zap is turned on and removes it when the Zap is turned off.
Common pitfalls
- Duplicates: a webhook can be replayed, by Zapier or by a user re-running an execution. Make processing idempotent, for example by storing the identifier of the received event.
- Personal data: every field you send passes through Zapier's servers. Send only what is strictly necessary and check the flow's GDPR compliance with your DPO.
- Business logic inside Zapier: complex filters and paths in a Zap quickly become impossible to test and version. Keep business rules in the application.
- Cost: per-task billing can climb fast on high-volume flows.
When not to use Zapier
For thousands of events per hour, a revenue-critical flow or a complex data transformation, an integration built directly between the two APIs remains more reliable, cheaper and easier to monitor. Zapier shines for internal automations, prototypes and flows that business teams need to be able to change on their own.
Best practices
- Secure webhooks with tokens
- Log every interaction for debugging
- Use queues for asynchronous processing
- Document APIs with OpenAPI for the Zapier integration
- Rate-limit public endpoints with Symfony's RateLimiter component
- Respond quickly with a 2xx status, then process
With a few well-designed, secure and asynchronous endpoints, your application becomes a building block that business teams can plug into their tools without asking developers for every new need.