Troubleshooting
Symptom first, for the things Pro adds. Anything about counters, quotas, plans or the credit ledger itself is in the free core’s troubleshooting page.
If an operation’s outcome is genuinely unknown rather than wrong, go straight to Recovery. This page is for the cases where something is visibly not happening.
No webhook is arriving at all
In order:
-
The endpoint is mounted after
Plug.Parsers, or with a routerforward. Signature verification needs the raw body, and a parser that has consumed it makes verification impossible: every delivery answers 400. Mountplug AuroraMeter.Pro.Webhook, path: "/webhooks/stripe"in the endpoint, abovePlug.Parsers. Getting started shows the placement. -
Every page of the app answers 400 “invalid signature”. The plug is in the
endpoint without
path:, so it treats every request as a Stripe event. Add the path. - Deliveries answer 200 and nothing is applied. The endpoint was created with the Thin payload style, whose events carry no object. Recreate it with the Snapshot style (a classic webhook endpoint always is).
-
The signing secret is the wrong one. Stripe issues a different secret per
endpoint, and the CLI’s
stripe listenissues another. A mismatch answers 400 and logs it. - Stripe is retrying and you are answering non-2xx. Check the endpoint’s delivery attempts in the Stripe dashboard. A redelivery is not an edge case: Stripe retries any non-2xx for days, which is why every handler is idempotent.
A webhook arrives and nothing happens
Some events are received and deliberately ignored, which is not a failure. The
required table in Getting started lists exactly the events
that do something; anything else falls through to :ignore on purpose.
Period rollover in particular is not driven by an invoice event. It comes
from the configured period source, so a missing invoice.paid is not why a
period did not turn over.
Usage is not reaching Stripe
Ask the outbox, not the reporter. Every intent is a row with a state.
AuroraMeter.Pro.Recovery.list_uncertain(limit: 20)
select state, count(*) from aurora_meter_outbox_items group by state;
What each state means for this symptom:
-
pendingwith anext_attempt_atin the future: backoff after a retry. Normal. The delay is computed, never taken from a provider header. -
pendingand nothing is moving: no deliverer is running. The Oban cron entry is the host’s to schedule. Operations has the schedule. -
quarantined: enqueue or a lease found a mapping, a mode or a period problem, and the software refused to guess. The row’squarantine_reasonsays which. Fix the cause, thenPro.Recovery.requeue_item/2. It is never released automatically. -
uncertain: the answer never arrived, or the attempt cap or the uncertainty horizon was reached. Recovery. -
rejected: terminal and visible. Stripe refused it for a reason recorded inlast_error.
A feature’s usage stopped at a particular date
That is what a source cutover looks like from the outside. A feature has one reporting source, and the changeover has a watermark: periods before it report from the old source and periods after it from the new one. If the new source has nothing enqueued, usage appears to stop at the watermark.
select feature, effective_period_start, state from aurora_meter_source_cutovers;
Cutover has the procedure and how to move a watermark that was set
wrong (Recovery‘s reschedule_cutover/2).
A payment is in an unknown state
Do not retry the charge. The pending payment machine persists the parameters and the idempotency key before the request, so the right move is to resume rather than to re-POST.
An empty search result in the Stripe dashboard is not proof that no charge occurred. Recovery says what evidence counts and which guarded operation applies.
Auto top-up stopped for a tenant
Three causes, and they are distinguishable:
-
A dispute. Any dispute, including an inquiry, drops the saved card and
stops recharge. That is deliberate product behaviour and needs
re_enable_auto_top_up/1once resolved. Top-ups. - Repeated declines. The failure count reached its limit and auto top-up disabled itself with a reason, which is kept on the account row.
-
No saved payment method. Enabling needs one, and the call answers
{:error, :no_payment_method}when there is not.
select tenant_key, auto_top_up_enabled, disabled_reason, failure_count
from aurora_meter_credit_accounts where tenant_key = $1;
The reconciliation list keeps growing
That is the system working as designed, and the list is meant to be read. Reconciliation reports differences; it does not repair them. An item is a question for a person, and closing it records who closed it and why.
AuroraMeter.Pro.Reconcile.list_items(state: "open", limit: 50)
Reconciliation has the kinds and what each one means.
The Pro dashboard page refuses to mount
By design. It shows outbox state, payments and tenant-level figures, so it needs
an explicit authorisation check and, unlike the core page, it does not accept
:host_route. Operations has the forms.
A webhook event’s API version does not match the pinned one
Recorded, carried in telemetry, logged once per distinct version, and never
refused: the event is a real fact about money and dropping it would be worse
than processing it. Stripe serialises an event at the endpoint’s version, or
the account default when the endpoint names none, and that is not the version
Pro sends on its own requests. Set webhook_api_version to :any to switch the
observation off, or align the endpoint in the Stripe dashboard.
The audit log is empty
The plug’s :tenant resolver returned nil, which is how unauthenticated
traffic stays out of the log, or the plug is not in the pipeline you think it
is. A write that fails is logged at warning and swallowed, because the audit
log never breaks the request it is auditing, so check your logs for that
warning before assuming nothing was attempted.
Still stuck
Email hello@aurorameter.com with the Pro and core versions, the Elixir and Erlang/OTP versions, the outbox or account row in question with identifiers redacted, and what you expected instead. A suspected vulnerability goes to SECURITY.md‘s private route, never to a public channel.