PingArk for Laravel
If you run Laravel, the plugin is the quickest way to start monitoring your scheduled tasks. Chain one method onto a task and PingArk knows when it starts, when it finishes, how long it took, and what went wrong if it failed. You never touch a ping URL by hand.
The plugin is open source and lives on GitHub and Packagist. Under the hood it just speaks the same ping API any job can use, so nothing here is magic. It is a thin, well-behaved wrapper that does the plumbing for you.
Requirements
- PHP 8.3 or newer
- Laravel 12 or 13
- A PingArk project, and its ping key
Install
Pull the package in with Composer.
composer require pingark/pingark-laravel
Copy
The service provider and the PingArk facade register themselves. Publishing the config file is optional, and only worth doing if you want to change a default.
php artisan vendor:publish --tag=pingark-configCopy
Configure
Add your project ping key to .env. You will find it in PingArk under your project's settings. That single value is all most applications need.
PINGARK_PING_KEY=your-project-ping-key
# Optional. Only needed for pingark:sync and the management API client.
PINGARK_API_KEY=your-read-write-api-key
Copy
The ping key is a capability secret. Anyone who has it can ping your checks, so keep it in your environment and out of version control, exactly like any other credential. The full list of options is in the config reference below.
The one-liner
Chain ->pingArk() onto any task in your schedule. That is the whole integration.
use Illuminate\Support\Facades\Schedule; Schedule::command('backup:run')->dailyAt('02:00')->pingArk();Copy
On each run the plugin sends a start ping before the task, a success ping after it finishes, and a failure ping if it fails. PingArk measures the run duration for you from the start and finish, and a failing command reports its real exit code along with whatever output it wrote, so an out-of-memory kill looks different from a plain error.
The check slug is derived from the task name, so backup:run pings a check named backuprun. When you would rather choose the slug yourself, pass it to ->pingArk(). The rest of this guide follows one example job, a nightly order import on the check nightly-import:
Schedule::command('import:orders')->dailyAt('02:00')->pingArk('nightly-import');Copy
Whichever slug you use must match a check in PingArk. Create it in the dashboard, or let pingark:sync create it for you.
Register your whole schedule
Rather than creating each check by hand, let the plugin mirror your entire schedule into PingArk in one go.
php artisan pingark:syncCopy
It creates one check per scheduled task, using the task's cron expression and timezone, and it skips any check that already exists, so it is safe to run again whenever you add new tasks. This needs PINGARK_API_KEY set to a read-write key. Run it without one and the command points you at creating a free account and the key you need; a successful sync ends with a link to the dashboard where your new checks live.
Preview first with --dry-run, which lists what would be created and changes nothing.
php artisan pingark:sync --dry-runCopy
Find checks that no longer map to a scheduled task with --prune. This only reports them. It never deletes anything, so a check you still want, perhaps one you ping another way, is never removed behind your back.
php artisan pingark:sync --pruneCopy
The PingArk facade
Sometimes you want to signal PingArk from inside a job rather than around a scheduled command. The PingArk facade gives you one method per signal.
use PingArk\Laravel\Facades\PingArk; PingArk::start('nightly-import'); // records a start time so duration can be measured PingArk::success('nightly-import'); // job finished, re-arms the check PingArk::fail('nightly-import'); // job failed, sends the check downCopy
If you set PINGARK_DEFAULT_CHECK, you can call these with no argument, which is handy in an app built around a single job.
Capture an exception on failure
Pass the caught exception to fail() and its class, message, and stack trace are attached to the failure, so you can see what went wrong right on the PingArk timeline.
use PingArk\Laravel\Facades\PingArk; try { $this->runImport(); PingArk::success('nightly-import'); } catch (\Throwable $e) { PingArk::fail('nightly-import', $e); throw $e; }Copy
Report an exit code
If you already have a process exit status, send it directly. Zero counts as success and any non-zero value counts as a failure, and the raw code is recorded either way.
PingArk::exitCode('nightly-import', 137); // 137 is an out-of-memory killCopy
Log a progress event
A log event records a note on the timeline without changing the check's state. It never arms, recovers, or alerts. Use it for progress inside a long job, so the timeline tells the whole story.
PingArk::log('nightly-import', 'processed 5,000 of 20,000 rows');Copy
Zero-setup checks
If you would rather not create a check first, PingArk can make one on the very first ping. Add ?create=1 to a ping URL for a slug that does not exist yet and the check is created and armed on the spot.
curl -fsS "https://ping.pingark.com/ping/your-project-ping-key/nightly-import?create=1"Copy
Auto-provisioned checks get a generic schedule you can refine later in the dashboard. When you want the schedule, timezone, and grace to be right from the start, use pingark:sync or the management API instead, both of which set them from your real schedule.
The management API client
For setup scripts and internal tooling, PingArk::api() gives you a small client for the management API. It needs a read-write PINGARK_API_KEY. Unlike the ping signals, the client throws on an error, because a failed setup call is something you want to know about.
use PingArk\Laravel\Facades\PingArk; $check = PingArk::api()->createCheck([ 'name' => 'Nightly import', 'slug' => 'nightly-import', 'schedule_type' => 'simple', 'period' => 86400, // expected every 24 hours 'grace' => 3600, // allow an hour late before alerting 'timezone' => 'UTC', ]); PingArk::api()->pause($check['id']); PingArk::api()->resume($check['id']); $pings = PingArk::api()->pings($check['id']); $flips = PingArk::api()->flips($check['id']); $channels = PingArk::api()->channels();Copy
The client covers the whole API: create, read, update, and delete checks, pause and resume them, and read back pings, status changes, and the project's channels. For the full field set and response shapes, see the Management API reference.
How it stays out of your way
Monitoring should never be the reason a job fails. Every ping the plugin sends has a short timeout and swallows its own errors, and it never retries. If PingArk is unreachable, your job runs exactly as it would without the plugin, and the next scheduled run pings again. When the plugin is disabled or the ping key is missing, the signals are a silent no-op, which is what you want on a laptop or a staging box.
The pingark:sync command and the PingArk::api() client are the deliberate exception. They are setup tools, not part of a running job, so they surface errors rather than hiding them.
Troubleshooting
- No pings arriving. Check that PINGARK_PING_KEY is set and that PINGARK_ENABLED is not false. With either missing, the plugin stays silent on purpose.
- Nothing on a machine you expect to be quiet. Set PINGARK_ENABLED=false on staging and local so only production reports. That is the intended way to mute an environment.
- A check set to accept only POST. The plugin sends a plain ping as a GET and a ping with a body as a POST. If you configured a check to accept only POST requests, use a signal that carries a body, or relax the filter on the check.
- Rate limited. A single check accepts a generous number of pings per minute. A tight loop that pings on every iteration can hit that limit; use a log event sparingly, or ping once per run.
- pingark:sync says it needs a key. Sync and the API client need a read-write PINGARK_API_KEY, which is separate from the ping key.
Still stuck? The ping API guide explains the signals in full, and the Management API reference covers everything the client can do.
Config reference
Every option lives in config/pingark.php and reads from the environment.
| Option | Env | Default | What it does |
|---|---|---|---|
| enabled | PINGARK_ENABLED | true | Master switch. Set to false to silence every ping. |
| base_url | PINGARK_BASE_URL | https://ping.pingark.com | The ingestion base URL your pings are sent to. |
| api_url | PINGARK_API_URL | https://api.pingark.com | The management API base URL, used by sync and the API client. |
| ping_key | PINGARK_PING_KEY | null | The project ping key your task pings hit. |
| api_key | PINGARK_API_KEY | null | A read-write key, used by sync and the API client. |
| default_grace | PINGARK_DEFAULT_GRACE | 600 | Grace period, in seconds, for checks created by sync. |
| timeout | PINGARK_TIMEOUT | 5 | Outbound ping timeout in seconds. Short, so a slow network never hangs a job. |
| user_agent | PINGARK_USER_AGENT | PingArk-Laravel | The user agent sent with every ping, so you can spot the plugin in ping history. |
| default_check | PINGARK_DEFAULT_CHECK | null | An optional fallback check slug, so the facade signals can be called with no argument. |