Weave Code
Weaver
Helps Laravel developers discover, compare, and choose open-source packages. See popularity, security, maintainers, and scores at a glance to make better decisions.
Feedback
Share your thoughts, report bugs, or suggest improvements.
Subject
Message

Laravel Zapier Laravel Package

jeffersongoncalves/laravel-zapier

A Laravel package that bridges your application with Zapier, letting you define webhook triggers and actions so events in your app can kick off automated workflows across thousands of connected services. Simple configuration keeps setup quick.

View on GitHub
Deep Wiki
Context7

Getting Started

1. Install and publish config

composer require <vendor>/laravel-zapier   # requires PHP 8.2+, Laravel 12/13
php artisan vendor:publish --tag=zapier-config   # confirm tag in the service provider

2. Wire credentials via env, never the config file. You need two distinct kinds of secret:

  • A personal/account API token for the REST API surface (zaps(), tasks(), profile(), â€Ĥ).
  • One catch-hook URL per automation you want to fire.
// config/zapier.php
return [
    'token' => env('ZAPIER_TOKEN'),
    'hooks' => [
        'new-lead'    => env('ZAPIER_HOOK_NEW_LEAD'),
        'invoice-paid'=> env('ZAPIER_HOOK_INVOICE_PAID'),
    ],
];

php artisan config:cache after adding a hook name — a name added to .env but not re-cached is the #1 "hook not found" cause.

3. First use case: fire one catch hook on a domain event. This is the lowest-risk, highest-value entry point — no OAuth, no Zapier account API permissions, just an outbound POST.

use Illuminate\Support\Facades\Zapier; // or inject the client

Zapier::send('new-lead', [
    'email'  => $lead->email,
    'source' => $lead->source,
]);

The same call accepts a raw URL, which is what you want when the hook is stored per-tenant in the database rather than in config:

Zapier::send($tenant->zapier_hook_url, $payload);

Where to look first: the published config file (for hook names and any timeout/retry/base-URL keys), then the client class behind the facade — check the exact argument order and return type of send() before you build on it, since the release notes describe behaviour rather than signatures.

Second step: the REST API methods are an admin/ops tool, not a request-path tool. Put zaps() behind an internal dashboard or an artisan command before you put enableZap() anywhere near user-facing code.

Implementation Patterns

Never call send() inline in a web request. The call is a synchronous outbound HTTP request to a third party; a slow or hanging hook becomes your latency. Queue it:

// app/Jobs/PushLeadToZapier.php
public function handle(): void
{
    Zapier::send('new-lead', $this->lead->toZapierPayload());
}

// dispatch from a listener, not a controller
class PushLeadToZapierListener
{
    public function handle(LeadCreated $event): void
    {
        PushLeadToZapier::dispatch($event->lead)->afterCommit();
    }
}

afterCommit() matters if the Zap reads back from your API on trigger — otherwise Zapier can fetch a record inside a transaction that rolled back.

Keep a thin application-level wrapper. Facade calls scattered across twenty classes make payload shape and error policy impossible to change. One App\Support\ZapierNotifier (or a small interface bound in a service provider that delegates to the package client) gives you a single place to normalize payloads, add tracing IDs, and no-op in local/CI.

Model the payload as a method on the model/event, not inline at the call site. toZapierPayload(): array keeps the field mapping testable and stops the same array being copy-pasted across jobs.

Multi-tenant = URL form, not hook names. Config-declared names are static per deployment; tenants need per-record hook URLs. Resolve the URL from the tenant model and pass it through send(), and treat a missing/blank URL as "tenant has not connected Zapier" rather than an error.

Environment separation via config names. Point new-lead at a staging catch hook in .env.staging and the production hook in .env.production. Same code path, different Zap, and you can test the whole chain without touching a customer's automations.

Admin tooling around the REST API. A realistic ops panel:

$zaps = Zapier::zaps();                 // list + status
Zapier::disableZap($zapId);             // pause a noisy automation during an incident
$usage = Zapier::tasks();               // task consumption → quota alerting

Wrap enableZap()/disableZap() in an audit log entry (who, which zap, when) — you are mutating state inside someone else's account, and "why did our Slack alerts stop" is a real support ticket.

Testing is the standout feature — use it. Because the client sits on Laravel's HTTP client, consumer tests need no SDK mocking:

Http::fake(['hooks.zapier.com/*' => Http::response('ok', 200)]);
Http::preventStrayRequests();

$this->post('/leads', [...])->assertRedirect();

Http::assertSent(fn ($req) => str_contains($req->url(), 'hooks.zapier.com')
    && $req['email'] === '[email protected]');

Pair preventStrayRequests() with the fake so a mis-typed hook URL fails loudly in CI instead of silently not firing. For the REST surface, fake api.zapier.com/* and assert method + path.

Dependency injection over the facade where it helps testability. The singleton is bound in the container, so constructor-injecting the client class (or an interface, if one ships) gives you a seam to swap in a decorator without touching call sites.

Gotchas and Tips

Singleton means config is read once per process. If you set config(['zapier.token' => ...]) at runtime after the client has been resolved (e.g. per-tenant credentials in a queued worker), it will not take effect. Resolve the client after configuration, or rebind it:

$this->app->forgetInstance(ZapierClient::class);

In long-running queue workers this is the difference between "works locally" and "every tenant uses the first tenant's token."

Catch hooks are fire-and-forget with weak failure signals. A 200 from hooks.zapier.com means Zapier accepted the request, not that the Zap's downstream action succeeded. Do not build correctness guarantees on the return value. If an action must not be lost, persist your own outbox row, mark it sent on 2xx, and build a retry command around it.

Retries can duplicate. Catch hooks have no idempotency key. Laravel's retry() or a job's $tries will re-POST the same payload and, if the first one did land, create a duplicate row in the user's spreadsheet/CRM. Prefer at-least-once with a dedupe key in the payload and a Filter step in the Zap over blind retries.

Hook names failing loudly vs quietly. A typo in the name argument will either throw (if the client validates against config('zapier.hooks')) or, worse, be treated as a URL. Assert the behaviour once in a test and guard hook names with a backed enum or constants so typos are compile-visible.

Hook URLs are bearer secrets. Anyone with the URL can trigger the Zap — no auth header is required. Keep them in .env/secret manager, exclude them from config:cache dumps committed to a repo, and scrub them from exception reporters and request logs. Same for the API token: never log outgoing headers.

Don't log the payload. Payloads are your customer data by design. Log an ID and the hook name, not the fields, or you have replicated PII into your log stack.

Timeouts and the default HTTP client. Set an explicit timeout (in the config if the package exposes one, otherwise via Http::globalOptions(['timeout' => 5]) in a service provider). Defaults are long enough to make a queue worker look hung when Zapier is slow.

Http::fake() needs your real host patterns. Faking api.zapier.com/* will not intercept hooks.zapier.com/* and vice-versa. Fake both, and remember Http::fake() in a base TestCase::setUp() is easily forgotten in a test that calls a controller indirectly dispatching the job — queued jobs don't run in feature tests unless you Queue::fake() or use Bus::fake(), so assert the job was pushed and test the job in isolation with the HTTP fake.

Guard destructive REST calls by environment. disableZap() in a staging test suite pointed at a production token silently kills a customer's automation. Assert ! app()->isProduction() before wiring enable/disable into any UI, and consider an explicit zapier.allow_writes config flag defaulting to false.

zaps()/tasks() are paginated and rate-limited. Don't render a full task list on page load; cache the response for a few minutes, handle the rate-limit response (429 + retry-after headers) explicitly, and paginate in your UI rather than in memory.

Watch the public REST API's stability. Zapier's account API is a much smaller, less-travelled surface than catch hooks and has historically been the part of the ecosystem to shift. Isolate those five methods behind your own service class so a future change is a one-file fix — and pin the package version rather than floating ^1.0.

Extension points worth knowing: rebind the client in a service provider (custom base URL for a Zapier-compatible proxy or a local recorder), use Http::globalRequestMiddleware() to attach a correlation header for tracing, and use the container's extend() to decorate the singleton without forking the package.

Weaver

How can I help you explore Laravel packages today?

Conversation history is not saved when not logged in.
Prompt
Add packages to context
No packages found.
htinlynn/genius-helper
maco-studios/hephaestus
elmogy/larafast
xeviant/laravel-paystack
gorilladash/laravel-website-sdk
martialbe/flysystem-oss
chondal/3312client
jeroen-g/flickr
opscale-co/nova-genbi
itsjustvita/laravel-retention
tequilarapido/trackit
gregsphotography/site-builder-filament
cabaoctavio/laravel-query-log
gregsphotography/site-builder-components
io-digital/clickatell
snowsoft/laravel-admin
oxygen/auth
relicsoftware/barakah-payment-gateway
tobya/work-with-composer
equipc/laravel-email-validator