Levy Cover Troubleshooting
This page covers the most common failure modes for Levy Cover and how to diagnose them. Most issues land in one of four buckets: configuration, carrier outage, webhook gaps, or accounting drift.
Opt-in card never appears at unlock
Symptom: Rider unlocks a vehicle, no Levy Cover card appears, the ride proceeds without an offer.
Most likely causes (in order):
- Subaccount toggle off. Check
/dashboard/insurance/settings. Confirm Levy Cover is enabled for the subaccount. - Jurisdiction not covered. The unlock location resolves to a region marked
available: falsein the matrix. See Jurisdiction Matrix. Check/api/admin/insurance/jurisdiction-matrixfor the current matrix state. - Carrier credentials missing.
CoverGeniusClientthrowsCarrierNotConfiguredError. In production this hides the card entirely. ConfirmCOVER_GENIUS_API_KEYandCOVER_GENIUS_PARTNER_IDare set on the deployed environment. - Carrier returned no tiers. The carrier can decline to offer coverage on specific risk profiles (e.g. a brand-new rider with no history in a high-risk zone). Check the quote response in logs.
- Quote timed out. The 2-second budget at unlock elapsed. The ride proceeds without the offer, which is the intended fail-open behavior.
- Rider preference set to "always decline." If the rider toggled the persistent preference off, the card is hidden until they re-enable.
Diagnostic queries:
SELECT * FROM insurance_offerings WHERE subaccount_id = '<id>'- confirm enabled rows.SELECT * FROM ride_insurance_policies WHERE ride_id = '<ride id>'- confirm whether a quote/bind actually happened.- Logs: search for
quote.requestedandquote.failedevents around the unlock timestamp.
Bind returned 200 but no policy in DB
Symptom: Logs show a successful POST /bookings call, but ride_insurance_policies has no row.
Likely causes:
- Webhook URL not registered. Some carrier integrations confirm the binding via the inbound
booking.confirmedwebhook. If that webhook never arrives, Levy logs the request as in-flight indefinitely. - Webhook signature failed. The event arrived but
signature_verified = false. Checkinsurance_webhook_log. - The bind succeeded at the carrier but the response payload was malformed. Rare; usually a sandbox-only issue.
Fix: Run the carrier reconciliation cron manually:
curl -X POST https://fleets.levyelectric.com/api/cron/insurance-carrier-reconciliation
The cron checks bound policies against the carrier and repairs missing ride insurance stamps. If the carrier reports the policy as valid, the row is created with bound_at backdated to the original bind time.
Premium not on the ride receipt
Symptom: The rider opted in, the policy was bound, but the receipt does not show the Levy Cover line item.
Causes:
applyRideInsuranceToPricingwas not called. Check thatsrc/lib/rides/process-ride-completion.tsis the path the completion took. The mobile-end, admin force-end, internal ride-end, and auto-end paths all apply this helper - if a new ride-end path was added, it must apply the helper too.- The bind happened but the carrier voided the policy before the ride ended. Check
ride_insurance_policies.voided_at. If set, the premium was correctly excluded from the receipt. - Stripe Connect charge failed. The premium is part of the ride total; if the total failed to charge, no line item is generated. Standard payment retry applies.
Diagnostic: SELECT id, total_cost, insurance_premium_amount FROM rides WHERE id = '<ride>'. If insurance_premium_amount is populated but total_cost does not include it, the completion pipeline did not apply the helper.
Claim photos fail to upload
Symptom: Rider tries to upload a photo on the claim screen, the upload errors out.
Causes:
insurance-claimsStorage bucket missing. Confirm the bucket exists with rider upload + read policies and service-role management policy. The migration20270601120100_09_*creates it.- Signed URL expired. Signed URLs for photo upload have a short TTL. If the rider sat on the screen too long, the URL expires. The app should re-fetch automatically; if it does not, restart the claim flow.
- File size or type rejected. The bucket policy restricts to image MIME types up to 25 MB per file.
Claim stuck in submitted for days
Symptom: A claim is filed, the operator sees it in the queue, but it never moves to under_review or further.
Causes:
- Webhook missed. The carrier moved the claim but the webhook never reached Levy. Check
insurance_webhook_logfor any failed deliveries around the time of expected status change. - Carrier-side adjuster delay. Cover Genius typically reviews within 48-72 hours. Slice can take longer in some jurisdictions.
- Required information missing. Some carriers send a
claim.requires_infowebhook that asks for additional data. Check the claim detail for a "carrier requesting" note.
Action: Operators can escalate to Levy support, who can chase the carrier through the partner channel. Operators cannot file appeals or status inquiries directly with the carrier.
Webhook signature failures
Symptom: insurance_webhook_log shows signature_verified = false.
Causes:
COVER_GENIUS_WEBHOOK_SECRETmismatch. Most common. Re-fetch the current secret from the Cover Genius portal and update the env var.- The wrong environment's webhook URL is registered. Sandbox events arriving at production endpoint, or vice versa. Re-register the correct URL in the carrier portal.
- Replay or out-of-order delivery. Duplicate-key handling on
UNIQUE (carrier, event_id)returns{ duplicate: true }. Genuine signature failures are different from duplicate handling - check the row'ssignature_verifiedfield.
Carrier outage
Symptom: Quote calls time out, bind calls return 5xx, the opt-in card stops appearing.
Behavior:
- Quotes time out -> fail open, ride proceeds without offer,
insurance_offered = false. - Existing bound policies remain valid. The carrier still honors them when claims are eventually filed.
- Pending claims sit in their current status until the carrier recovers and processes them.
Action:
- Confirm the outage on the Cover Genius status page or via the partner channel.
- Communicate to affected operators if the outage exceeds 1 hour.
- Do not manually bind policies during the outage. There is no path to do this safely - if the carrier has not actually received the binding, the ride is uninsured.
Premium charged but rider claims they did not opt in
Symptom: Rider support ticket says "I did not opt into insurance but I was charged."
Diagnosis:
- Check
ride_insurance_policiesfor the ride. If a row exists with abound_attimestamp, an opt-in event was recorded. - Check the
policy_wording_versionfield for the wording the rider was shown. - Check the rider's persistent preference - "Remember my choice" set to accept means future rides auto-bind.
Resolution:
- If the rider genuinely did not opt in (a UI bug or a stale auto-include preference), follow the premium refund path. See Premium Refunds via the Ride.
- If the rider opted in but forgot - the wording was clear and the binding stamp is intact - explain the receipt line item and the preference toggle. No refund.
Tests are failing
The full test suite is at src/lib/insurance/__tests__/. There are 8 files and 36 tests. To run them:
npx vitest run src/lib/insurance/__tests__/ src/lib/rides/__tests__/durable-completion-jobs.test.ts
Common failures:
- Env vars set during test runs. If the test environment has
COVER_GENIUS_API_KEYset, the registry may pick the real client instead of the mock. UsesetCarrierRegistryForTeststo pin. - Migration drift. If a developer edits the migration files locally, the test fixtures can drift from the schema. Reset the local DB and re-run.
Next
Check FAQ for shorter answers to common questions, or Carrier Credentials Setup for env-var-related issues.
Need help?
If none of the above resolves the issue, contact support@levyelectric.com with the ride ID and any relevant log timestamps.