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.
Running a fleet on this hardware?
Levy Fleets runs scooters, e-bikes, mopeds and golf carts on the IoT hardware you already have, with integration pages for 29 vendors including Segway, Okai, Omni, Queclink, Invers and Comodule. Keyless unlock, geofencing, rider payments and rider support are included, and the Managed plan is a share of revenue with no per-vehicle software fee.