Skip to content

Sirloin Errors

The errors operators and clients see most often, with code references and remediation. Domain billing errors are defined in apps/sirloin/internal/app/services/billing/domain/errors.go. Cross-cuts: security-model for auth-related errors, observability for log/trace lookup.

Most billing errors are wrapped by BillingError (Op, Entity, Err, Details) — search Axiom by Op="checkout" etc. when triaging.

Domain Errors

ErrorWhen raisedMeaningRemediationSource
subscription not foundRPCs that resolve a subscription by idCaller passed a stale/foreign id, or DB ↔ Chargebee driftVerify id in admin (strip); reconcile via cmd/scripts/wallet-recovery-*billing/domain/errors.go
invoice not foundPayment recording, dunning retryChargebee invoice id missing locallyRe-run event poller or call SubmitPaidInvoicebilling/domain/errors.go
customer not foundAny subscription opClerk user lacks Chargebee customerRecreate via checkout flowbilling/domain/errors.go
coupon not foundValidateCoupon, checkoutCoupon code invalid or removed in ChargebeeCaller should clear inputbilling/domain/errors.go
item price not foundListProducts, checkoutPlan/addon price missing in ChargebeeSync plans (cmd/scripts/backfill-purchase-prices)billing/domain/errors.go
payment not foundGetPaymentPrimer/Chargebee payment missingWait for poller; verify transactionIDbilling/domain/errors.go
invalid subscription stateCancel / reactivate / downgradeState machine refuses transitionRead billing-pitfalls; fix in adminbilling/domain/errors.go
invalid invoice statePayment recordingInvoice already paid/voidedNo action; idempotent retry safebilling/domain/errors.go
subscription is not activeUsage-gated RPCs (GenerateMedia)Sub is future, cancelled, or pausedDirect user to checkout / reactivationbilling/domain/errors.go
payment currency does not match invoice currencyPayment recordingPrimer settled in non-matching currencyManual reconcile in Chargebeebilling/domain/errors.go
service temporarily unavailableAny Chargebee/Primer callCircuit breaker openWait, retry; check provider statusbilling/domain/errors.go
rate limited by downstream serviceChargebee/Primer 429Upstream throttledBackoff; reduce poller cadence if sustainedbilling/domain/errors.go

Webhook Errors

StatusPathCauseRemediation
401 Unauthorized/webhooks/chargebee/eventsBasic auth header missing or wrongSet SIRLOIN_CHARGEBEE_WEBHOOK_USERNAME / _PASSWORD; reconfigure Chargebee endpoint creds
401 Unauthorized/webhooks/primer/disputes, /webhooks/primer/paymentsHMAC mismatch or signedAt outside 3-min skewRe-verify SIRLOIN_PRIMER_WEBHOOK_SECRET; check NTP drift on host
401 Unauthorized/webhooks/ondato/verification-statusShared-secret mismatchVerify SIRLOIN_ONDATO_WEBHOOK_SECRET
405 Method Not Allowedany webhookCaller used GETUse POST
413 Payload Too Large/webhooks/primer/disputesBody > 1 MiBTrim payload upstream; otherwise no path

Source: internal/app/services/billing/webhooks/chargebeewebhook.go, internal/app/services/billing/disputes/primerwebhook.go.

gRPC Errors

Sirloin maps domain errors to gRPC status codes at the handler edge. Common mappings (TODO(@zen): exhaustive table — derive from each handler):

gRPC codeTypical causeExamples
NOT_FOUNDDomain *NotFound errorssubscription/invoice/customer/coupon
FAILED_PRECONDITIONinvalid * state, subscription is not activeusage-gated RPCs
UNAVAILABLECircuit breaker open (ErrServiceUnavailable)Chargebee/Primer outage
RESOURCE_EXHAUSTEDErrRateLimitedupstream 429
UNAUTHENTICATEDMissing/invalid Clerk tokenbrisket / strip handlers
PERMISSION_DENIEDStrip RPC without admin roleStripService

Startup Errors

Error / logMeaningRemediation
MissingError("SIRLOIN_DATABASE_URL")DB DSN unsetSet env var; sirloin refuses to start
using default port :50051 (warn)SIRLOIN_PORT unsetAcceptable in dev
Chargebee webhook credentials not set (warn)Username or password emptyWebhooks will return 401 until set
Flank encryption key not set (warn)(legacy) FlankStorageService disabled — superseded by brain’s workflow APISafe to ignore on the brain-backed path; set key only if still using the retired flank storage surface
Flank service token not set (warn)(legacy) FlankResolveSecrets rejects all — superseded by brainSafe to ignore on the brain-backed path; set token only if still using the retired surface
Primer API key not set (warn)Primer checkout disabledSet key for prod
Primer webhook secret not set (warn)Primer webhooks rejectedSet secret

Source: apps/sirloin/internal/app/config/config.go.

Upstream-Propagated Errors

Chargebee and Primer errors surface inside BillingError.Err with the provider’s status / code. Recurring patterns:

Provider errorWhat it meansAction
Chargebee 404Resource not in Chargebee (returned as nil, not an error — see billing-pitfalls)Treat as *NotFound; don’t retry
Chargebee 429Rate limitedBackoff; align poller
Chargebee 5xxProvider outageCircuit breaker opens; alerts fire
Primer Authentication failedBad API key or wrong env (sandbox vs prod)Verify SIRLOIN_PRIMER_API_KEY prefix
Primer Idempotency key conflictReusing the wrong keyUse transactionID per billing-pitfalls

Resolved

  • SubmitPaidInvoice does not distinguish “already credited” from “invoice not found” at the gRPC code level. Already-processed payments are treated as success (idempotent: PurchaseExists short-circuits and returns an empty SubmitPaidInvoiceResponse). Missing payment/invoice returns NotFound. Other status mappings: Unavailable for errPrimerPaymentStillProcessing, FailedPrecondition for errPrimerPaymentFailed, Internal for everything else (apps/sirloin/internal/app/services/billing/submitpaidinvoice.go:182-:194, :351-:368, :238-:240).

TODO(@zen)

  • TODO(@zen): exhaustive gRPC code mapping per RPC.
  • TODO(@zen): full enumeration of BillingError Op values used in production.