ConvertKit - v1.0.0
Errors
Every Kit-side failure surfaces as a typed exception in the ArtisanPackUI\ConvertKit\Api\Exceptions namespace. All five extend KitException, which itself extends RuntimeException.
Catch KitException to catch everything; catch a specific subclass to route by failure kind.
Hierarchy
KitException (base)
├── KitAuthException — 401/403 from Kit
├── KitRateLimitException — 429 (or Retry-After too long)
├── KitValidationException — 422 with per-field errors
├── KitNotFoundException — 404
└── KitServerException — 5xx
KitAuthException
- When: Kit returned
401 Unauthorizedor403 Forbidden. - Cause: bad or missing API key, revoked key, or the key doesn't have permission for the endpoint.
- Retry?: No. Retrying with the same key will just fail again and waste quota.
Fix:
- Confirm
CONVERTKIT_API_KEYis set and matches your Kit account. - Run
php artisan convertkit:testto verify. - If you recently rotated the key, restart workers so the new value is picked up.
The client refuses to send the API key if base_url isn't https:// unless CONVERTKIT_ALLOW_INSECURE_HTTP=true is explicitly set. That protects you from an accidental config that would leak credentials over plaintext.
KitRateLimitException
- When: Kit returned
429 Too Many Requests, or theRetry-Afterheader asked for a wait longer thanconvertkit.max_retry_after(default 60s). - Cause: too many requests too fast, or Kit is doing platform-level backpressure.
- Retry?: Yes. The client retries automatically with exponential backoff (
retries,retry_delay,max_backoff) and honorsRetry-Afterup to the max. If retries are exhausted, this bubbles to the caller.
Fix:
- If you see this frequently, raise
CONVERTKIT_RETRIESor lower your request rate (batch subscribes, add a queue). - The forms integration's
ProcessKitFeedjob re-throws this so the queue worker retries — no code change needed.
KitValidationException
- When: Kit returned
422 Unprocessable Entity. - Cause: payload is malformed. Malformed email, unknown custom-field key, invalid
kit_form_id, etc. - Retry?: No. The payload will keep failing.
Fix:
- Log the exception message — Kit typically explains which field failed.
- If it happened via a feed, check the feed's
field_map: are all the destination keys valid Kit custom fields? Runphp artisan convertkit:sync fieldsand cross-reference. - If it happened via
forms()->subscribe(), confirmkit_form_idexists (ConvertKit::forms()->list()).
The KitSubscriptionFailed event fires when this comes out of a queued job. Route to your alerting stack — feed misconfig is usually the cause and needs human attention.
KitNotFoundException
- When: Kit returned
404 Not Found. - Cause: the entity you addressed doesn't exist. A
kit_form_idthat was deleted, a subscriber id from a stale record, etc. - Retry?: No.
Fix:
subscribers()->findByEmail()returnsnullinstead of throwing — reach for it when you're not sure the subscriber exists.- For
find()andupdate()calls, wrap in a try/catch if the entity might have been deleted between lookup and action.
KitServerException
- When: Kit returned
500,502,503, or504. - Cause: Kit-side outage or blip.
- Retry?: Yes. The client retries automatically with exponential backoff.
Fix:
- Nothing you can do server-side. If retries are exhausted, this bubbles;
ProcessKitFeedwill re-throw and the queue worker will retry per its own retry policy. - If Kit is fully down for long enough that queue retries drain, check Kit's status page.
Catching them
Everything:
use ArtisanPackUI\ConvertKit\Api\Exceptions\KitException;
try {
ConvertKit::subscribers()->create( 'jane@example.com' );
} catch ( KitException $e ) {
report( $e );
}
Route by kind:
use ArtisanPackUI\ConvertKit\Api\Exceptions\KitAuthException;
use ArtisanPackUI\ConvertKit\Api\Exceptions\KitRateLimitException;
use ArtisanPackUI\ConvertKit\Api\Exceptions\KitValidationException;
try {
ConvertKit::subscribers()->create( 'jane@example.com' );
} catch ( KitAuthException $e ) {
// Page an on-call engineer — bad or rotated key.
} catch ( KitRateLimitException $e ) {
// Log; the client already retried.
} catch ( KitValidationException $e ) {
// Report the exception; the request was malformed.
}
Retry policy at a glance
| Exception | Client retries? | Queue job retries? |
|---|---|---|
KitAuthException |
No | No — job fails terminally. |
KitRateLimitException |
Yes | Yes — re-thrown from handle(). |
KitValidationException |
No | No — job fails terminally. |
KitNotFoundException |
No | No — job fails terminally. |
KitServerException |
Yes | Yes — re-thrown from handle(). |
Non-Kit exceptions
FieldMapperException— the field mapper could not produce a payload. Caught by the listener →KitFeedSkippedevent.RuntimeExceptionfromKitFeed::form()— the configuredform_modeldoesn't exist. Fix: installartisanpack-ui/formsor pointCONVERTKIT_FORMS_MODELat a real class.\Illuminate\Contracts\Encryption\DecryptException— Laravel's encrypter can't decrypt a token. Usually meansAPP_KEYwas rotated without re-encrypting stored data.