Laravel cookbook · Intermediate
Respect third-party API rate limits in queued jobs
A burst of jobs hits a partner API's rate limit and most of them fail.
Approach
Define a named rate limiter and attach the RateLimited job middleware; jobs over the limit are released back
onto the queue instead of failing. Combine with retryUntil so jobs keep trying for a bounded time.
Solution
<?php
// app/Providers/AppServiceProvider.php (boot)
use Illuminate\Cache\RateLimiting\Limit;
use Illuminate\Support\Facades\RateLimiter;
RateLimiter::for('crm-api', fn (object $job) => Limit::perMinute(60));
<?php
// app/Jobs/PushContactToCrm.php
namespace App\Jobs;
use App\Models\Contact;
use Illuminate\Contracts\Queue\ShouldQueue;
use Illuminate\Foundation\Queue\Queueable;
use Illuminate\Queue\Middleware\RateLimited;
use Illuminate\Support\Facades\Http;
class PushContactToCrm implements ShouldQueue
{
use Queueable;
public function __construct(public Contact $contact) {}
public function middleware(): array
{
return [new RateLimited('crm-api')];
}
public function retryUntil(): \DateTimeInterface
{
return now()->addHours(2);
}
public function handle(): void
{
Http::withToken(config('services.crm.token'))
->timeout(10)
->put("https://crm.example.com/contacts/{$this->contact->external_id}", [
'email' => $this->contact->email,
'name' => $this->contact->name,
])
->throw();
}
}
Pitfalls
- Using
$trieswith rate limiting; every release counts as an attempt, so jobs fail quickly. UseretryUntil. - A cache store that isn't shared between workers, so each worker has its own limit.
- Rate limiting per job class when the partner limits per account; key the limiter by account.
Variations
- Return
Limit::perMinute(60)->by($job->contact->account_id)for per-tenant limits. - Use
RateLimitedWithRedisfor better performance on Redis.
Further reading: Job rate limiting
Reference: https://laravel.com/docs/13.x/queues#rate-limiting
More Laravel recipes
- Authorize actions with policies
- Cache expensive data with stale-while-revalidate
- Call external APIs with timeouts, retries and fakes
- Catch N+1 queries with strict Eloquent mode
- Delete old records automatically with prunable models
- Dispatch events and listeners after the transaction commits
- Issue scoped API tokens with Sanctum
- Make queued jobs reliable with retries, backoff and uniqueness