marin-solutions/checkybot-laravel
Laravel integration for CheckyBot that makes it simple to add uptime, cron and health monitoring to your application. Connect your app with minimal configuration and get timely alerts when checks fail, so you can catch issues before they affect users without adding complexity to your codebase.
Checkybot is a Laravel package for defining HTTP/API checks that a Checkybot server executes on your behalf. The PHP side is a fluent definition layer; the actual request and assertion evaluation happen server-side. That split matters for everything below — your Laravel app describes what to check, the server does the checking.
Install and publish config first:
composer require checkybot/checkybot
php artisan vendor:publish --tag=checkybot-config
Then confirm two things before writing any checks:
config/checkybot.php (or the matching env vars) point at a reachable Checkybot server.ApiCheck::source(), which is only honoured by a server with source support. A client newer than the server will silently ignore the chaining rather than fail loudly — see Gotchas.Where to look first: the package's check-definition classes (the ApiCheck builder) and the config file. The builder is the whole public API surface; there is very little else to learn.
First use case — the one you should get working before touching chaining:
ApiCheck::make('Health endpoint')
->url('https://api.example.com/health')
->method('GET')
->assertStatus(200)
->assertJsonPath('status', 'ok');
Register it wherever the package expects checks (a service provider, a dedicated checks.php, or a scheduled command — check the package's own docs for the registration point, as this is the part most likely to differ between minor versions). Run it, confirm it appears on the server, then move on to source().
The cheapest mistake to make is an over-broad assertion — assertStatus(200) alone on an endpoint that returns 200 with an error body. Pair a status assertion with at least one body assertion, and name checks so an alert is self-explanatory:
ApiCheck::make('Checkout: cart totals endpoint returns priced cart')
->url('https://api.example.com/v1/cart/{cart_id}')
->assertStatus(200)
->assertJsonPath('total.currency', 'GBP');
source() — the v0.2.3 patternApiCheck::source($url, $path) exists to solve the "I need an ID/token/slug from a previous response" problem. The server calls the source URL, reads $path out of the response, and substitutes it into {source} in both the check URL and assertion expected values.
ApiCheck::make('Order detail resolves for a real order')
->source('https://api.example.com/v1/orders?limit=1', 'data.0.id')
->url('https://api.example.com/v1/orders/{source}')
->assertStatus(200)
->assertJsonPath('data.id', '{source}');
Three things this unlocks, in rough order of day-to-day value:
1. Referential integrity checks. Asserting that the ID you fetched is the ID you got back is a genuinely useful smoke test — it catches routing/tenant-isolation bugs that a fixed-ID check never would.
2. Fixture-free checks. No hardcoded IDs that rot when a seed changes. The source resolves a live value each run.
3. Assertion-level substitution. Because {source} is replaced in expected values too, you can assert equality against a dynamically-derived value without duplicating the path in your PHP:
->source('https://auth.example.com/v1/me', 'tenant.slug')
->url('https://api.example.com/v1/tenant/{source}/settings')
->assertJsonPath('tenant.slug', '{source}');
Use that form deliberately — an assertion that X == X because both sides came from the same source is a tautology and tests nothing about the tenant endpoint. Prefer asserting derived facts (a status, a permission flag, a related field) rather than echo-back equality.
Treat each source() call as a dependency with its own latency and failure mode. Favour:
POST/mutation endpoint.source(). A ^0.2 constraint is reasonable; a floating constraint against a package whose behaviour is server-negotiated is asking for a silent regression.The release note says it plainly: needs a Checkybot server with source support. What it doesn't say is what an unsupported server does with {source}. Assume the worst case — the placeholder is passed through literally as a URL segment (/v1/orders/%7Bsource%7D), the request 404s, and you get an alert that looks like an application bug rather than a version mismatch.
Mitigations, in order:
UPGRADING.md note).$path in source(url, path) is resolved by the server, so it is not Laravel's Arr::get() and not dot-notation-identical to anything you're used to. data.0.id may or may not be the array-index syntax the server expects — it could be data[0].id, a JSONPath like $.data[0].id, or something bespoke. Test one path against real data before writing twenty checks with the same syntax.
Practical debugging: temporarily assert the raw path in a throwaway check and read the failure output. A "source resolved to empty" error usually means the path is wrong, not that the endpoint is down.
The nastiest failure mode is a source that succeeds but returns nothing useful:
orders?limit=1 returns {"data": []} → path resolves to null/empty → check URL becomes /v1/orders/ (trailing slash) → you're now testing your collection endpoint by accident, and it may well return 200, so the check passes while testing nothing.[0] gives you a different value each run. The check becomes non-deterministic and will eventually page you at 3am for no reason.Guard against both: prefer sources with a deterministic, single result (a /me, /config, /health-style endpoint), and be suspicious of any check where a passing result doesn't clearly correspond to a real assertion.
Chained checks have two round trips. If the source times out, the check fails for a reason unrelated to the endpoint under test — and both alerts will likely fire together, doubling noise. Where the server exposes timeouts or retry settings, tune the source fetch tighter than the check itself, and make sure your alert routing groups related checks rather than paging per-check.
Source URLs are often auth or token-issuing endpoints. Don't inline credentials into source() arguments — read them from config/env and let them flow through, so the definition stays safe to commit and safe to display in a check dashboard. The same applies to expected values: a {source}-substituted assertion is fine, a hardcoded API key in an assertion is not.
Because the substitution happens server-side, you cannot debug it with dd() or Xdebug through the package. Your levers are:
| Concern | Advice |
|---|---|
| Client constraint | Pin to ^0.2, upgrade deliberately |
| Server support | Verify before adopting source() |
| Rollout | Canary chained check, separate deploy from the client bump |
| Rollback | Keep chained checks in a separable definition block |
The v0.2.3 feature is a small API addition with a large operational dependency. The code is one line; the risk is entirely in the version pairing and in path syntax you can't introspect from PHP. Get the canary right and chained checks are a genuine upgrade over hardcoded IDs.
How can I help you explore Laravel packages today?