moeru-ai/airi · error · ApiError

STRIPE_NOT_CONFIGURED

STRIPE_NOT_CONFIGURED

Error message

Stripe is not configured

What it means

The webhook operation requires an initialized Stripe client and a configured webhook signing secret. If either is missing, incoming webhook POSTs cannot be verified or processed, so it responds with 503 STRIPE_NOT_CONFIGURED before even checking the signature.

Solutions

  1. Set STRIPE_WEBHOOK_SECRET (whsec_...) from the Stripe dashboard webhook endpoint settings.
  2. Ensure the Stripe client initializes (STRIPE_SECRET_KEY present and valid).
  3. Restart the API after adding the configuration.
  4. If testing locally, use the Stripe CLI to forward events with a local secret configured.

Example fix

// before (.env)
# STRIPE_WEBHOOK_SECRET missing
// after
STRIPE_WEBHOOK_SECRET=whsec_xxx
Defensive patterns

Strategy: try-catch

Validate before calling

// Server-side startup check
if (!process.env.STRIPE_SECRET_KEY || !process.env.STRIPE_WEBHOOK_SECRET)
  throw new Error('Stripe webhook not configured')

Try / catch

try {
  await forwardWebhook(signature, body)
} catch (e) {
  if (e.code === 'STRIPE_NOT_CONFIGURED') {
    // do not retry; configure the environment first
  }
}

Prevention

When it happens

Trigger: Stripe POSTing to /webhooks/stripe (or a developer curling it) while stripe is null (no STRIPE_SECRET_KEY at init) or webhookSecret is null (STRIPE_WEBHOOK_SECRET unset).

Common situations: Deploying without setting STRIPE_WEBHOOK_SECRET; running the API locally without any Stripe environment; secret removed during rotation; webhook endpoint registered before backend config completed.

Understand the failure class

Background: "environment variable is not set" and "Missing keys in environment" errors: what missing required env var messages mean and how to fix them — this error's family across 28 libraries.

Related errors


AI-assisted analysis of moeru-ai/airi@438a067dde (2026-09-17). Data as JSON: /api/errors/cf225c9f734a0d85. Report an issue: GitHub.

Appendix: source

Thrown at server/apps/api/src/routes/stripe/operations/webhook.ts:94

  }).onConflictDoNothing()
  return legacy.id
}

/**
 * Verifies a Stripe webhook, maps a Checkout Session to a claim receipt,
 * then calls Payment CORE. Unknown events are ignored.
 */
export function createWebhookOperation(
  stripe: Stripe | null,
  webhookSecret: string | null,
  payment: PaymentService,
  db: Database,
  metrics: RevenueMetrics | null,
  productEventService: ProductEventService | null,
) {
  return async (signature: string | null, body: string): Promise<{ received: true }> => {
    if (!stripe || !webhookSecret)
      throw createServiceUnavailableError('Stripe is not configured', 'STRIPE_NOT_CONFIGURED')

    if (!signature)
      throw createBadRequestError('No signature', 'MISSING_SIGNATURE')

    let event: Stripe.Event
    try {
      event = stripe.webhooks.constructEvent(body, signature, webhookSecret)
    }
    catch (err: unknown) {
      throw createBadRequestError(`Webhook Error: ${errorMessageFromUnknown(err)}`, 'WEBHOOK_ERROR')
    }

    logger.withFields({ type: event.type, id: event.id }).log('Webhook event received')
    metrics?.stripeEvents.add(1, { event_type: event.type })

    switch (event.type) {
      case 'checkout.session.completed':
      case 'checkout.session.async_payment_succeeded': {

View on GitHub (pinned to 438a067dde)