Omnibase

Webhooks

Configure Stripe webhook endpoints for real-time event handling

Webhooks

Webhooks allow your application to receive real-time notifications about events in your Stripe account, such as successful payments, subscription changes, and invoice updates.

Webhook Configuration

Basic Webhook Setup

{
  "webhooks": [
    {
      "id": "main_webhook",
      "url": "${STRIPE_WEBHOOK_URL}/api/stripe/webhook",
      "events": [
        "customer.subscription.created",
        "customer.subscription.updated",
        "customer.subscription.deleted",
        "invoice.paid",
        "invoice.payment_failed"
      ],
      "connect": false
    }
  ]
}
FieldTypeRequiredDescription
idstringNoUnique identifier for the webhook
urlstringYesWebhook endpoint URL
eventsstring[]YesList of events to subscribe to (minimum 1)
connectbooleanNoListen to Stripe Connect events (default: false)

Environment Variable Interpolation

Use ${VAR_NAME} syntax for environment-specific URLs:

{
  "webhooks": [
    {
      "id": "main_webhook",
      "url": "${STRIPE_WEBHOOK_URL}/api/stripe/webhook",
      "events": ["invoice.created"]
    }
  ]
}

Set the environment variable:

.env
STRIPE_WEBHOOK_URL=https://api.example.com

Supported Events

OmniBase supports 60+ Stripe webhook events organized by category:

Account Events

EventDescription
account.updatedAccount details changed
account.application.deauthorizedOAuth application disconnected
account.external_account.createdBank account/card added
account.external_account.deletedBank account/card removed
account.external_account.updatedBank account/card updated

Customer Events

EventDescription
customer.createdNew customer created
customer.updatedCustomer details updated
customer.deletedCustomer deleted

Subscription Events

EventDescription
customer.subscription.createdNew subscription started
customer.subscription.updatedSubscription changed
customer.subscription.deletedSubscription canceled
customer.subscription.pausedSubscription paused
customer.subscription.resumedSubscription resumed
customer.subscription.trial_will_endTrial ending soon (3 days)

Payment Events

EventDescription
payment_intent.createdPayment intent created
payment_intent.succeededPayment successful
payment_intent.payment_failedPayment failed
payment_intent.canceledPayment canceled
payment_intent.requires_actionRequires customer action (3DS)

Charge Events

EventDescription
charge.succeededCharge successful
charge.failedCharge failed
charge.refundedCharge refunded
charge.capturedCharge captured
charge.dispute.createdDispute opened
charge.dispute.updatedDispute updated
charge.dispute.closedDispute closed

Invoice Events

EventDescription
invoice.createdDraft invoice created
invoice.finalizedInvoice finalized
invoice.paidInvoice paid
invoice.payment_failedPayment failed
invoice.payment_action_requiredRequires customer action
invoice.upcomingUpcoming invoice notification
invoice.marked_uncollectibleMarked uncollectible
invoice.voidedInvoice voided

Checkout Events

EventDescription
checkout.session.completedCheckout completed
checkout.session.expiredCheckout expired
checkout.session.async_payment_succeededAsync payment succeeded
checkout.session.async_payment_failedAsync payment failed

Product & Price Events

EventDescription
product.createdProduct created
product.updatedProduct updated
product.deletedProduct archived
price.createdPrice created
price.updatedPrice updated
price.deletedPrice archived

Other Events

EventDescription
payout.createdPayout initiated
payout.paidPayout completed
payout.failedPayout failed
refund.createdRefund created
refund.updatedRefund updated
payment_method.attachedPayment method attached
payment_method.detachedPayment method detached
transfer.createdTransfer created
transfer.reversedTransfer reversed

Stripe Connect Webhooks

For platforms using Stripe Connect, set connect: true to receive events from connected accounts:

{
  "webhooks": [
    {
      "id": "platform_webhook",
      "url": "${WEBHOOK_URL}/api/stripe/webhook",
      "events": [
        "account.updated",
        "payment_intent.succeeded"
      ],
      "connect": false
    },
    {
      "id": "connect_webhook",
      "url": "${WEBHOOK_URL}/api/stripe/connect-webhook",
      "events": [
        "account.updated",
        "payment_intent.succeeded",
        "payout.paid"
      ],
      "connect": true
    }
  ]
}

Webhook Secrets

After configuring webhooks, retrieve the signing secrets:

CLI

omnibase stripe webhook secret

API

GET /api/v1/stripe/admin/webhooks
X-Service-Key: your-service-key

Response:

{
  "webhooks": [
    {
      "id": "main_webhook",
      "stripe_id": "we_1ABC123...",
      "url": "https://api.example.com/api/stripe/webhook",
      "events": ["invoice.paid", "customer.subscription.created"],
      "connect": false,
      "secret": "whsec_xxxxxxxxxxxxx"
    }
  ]
}

Handling Webhooks

Signature Verification

Always verify webhook signatures to ensure events are from Stripe:

app/api/stripe/webhook/route.ts
import Stripe from 'stripe';
import { headers } from 'next/headers';

const stripe = new Stripe(process.env.STRIPE_SECRET_KEY!);
const webhookSecret = process.env.STRIPE_WEBHOOK_SECRET!;

export async function POST(request: Request) {
  const body = await request.text();
  const headersList = await headers();
  const signature = headersList.get('stripe-signature')!;

  let event: Stripe.Event;

  try {
    event = stripe.webhooks.constructEvent(body, signature, webhookSecret);
  } catch (err) {
    console.error('Webhook signature verification failed');
    return new Response('Invalid signature', { status: 400 });
  }

  switch (event.type) {
    case 'customer.subscription.created':
      const subscription = event.data.object as Stripe.Subscription;
      // Handle new subscription
      break;
    case 'invoice.paid':
      const invoice = event.data.object as Stripe.Invoice;
      // Handle paid invoice
      break;
    case 'invoice.payment_failed':
      const failedInvoice = event.data.object as Stripe.Invoice;
      // Handle failed payment
      break;
    default:
      console.log(`Unhandled event type: ${event.type}`);
  }

  return new Response('OK', { status: 200 });
}
internal/handlers/stripe_webhook.go
package handlers

import (
    "encoding/json"
    "io"
    "net/http"
    "os"

    "github.com/gin-gonic/gin"
    "github.com/stripe/stripe-go/v82/webhook"
)

func HandleStripeWebhook(c *gin.Context) {
    body, err := io.ReadAll(c.Request.Body)
    if err != nil {
        c.JSON(http.StatusBadRequest, gin.H{"error": "Failed to read body"})
        return
    }

    signature := c.GetHeader("Stripe-Signature")
    webhookSecret := os.Getenv("STRIPE_WEBHOOK_SECRET")

    event, err := webhook.ConstructEvent(body, signature, webhookSecret)
    if err != nil {
        c.JSON(http.StatusBadRequest, gin.H{"error": "Invalid signature"})
        return
    }

    switch event.Type {
    case "customer.subscription.created":
        var subscription stripe.Subscription
        json.Unmarshal(event.Data.Raw, &subscription)
        // Handle new subscription

    case "invoice.paid":
        var invoice stripe.Invoice
        json.Unmarshal(event.Data.Raw, &invoice)
        // Handle paid invoice

    case "invoice.payment_failed":
        var invoice stripe.Invoice
        json.Unmarshal(event.Data.Raw, &invoice)
        // Handle failed payment
    }

    c.JSON(http.StatusOK, gin.H{"received": true})
}

Common Webhook Patterns

Subscription Lifecycle

{
  "webhooks": [
    {
      "id": "subscription_events",
      "url": "${WEBHOOK_URL}/api/webhooks/subscriptions",
      "events": [
        "customer.subscription.created",
        "customer.subscription.updated",
        "customer.subscription.deleted",
        "customer.subscription.paused",
        "customer.subscription.resumed",
        "customer.subscription.trial_will_end"
      ]
    }
  ]
}

Payment Monitoring

{
  "webhooks": [
    {
      "id": "payment_events",
      "url": "${WEBHOOK_URL}/api/webhooks/payments",
      "events": [
        "payment_intent.succeeded",
        "payment_intent.payment_failed",
        "charge.refunded",
        "charge.dispute.created"
      ]
    }
  ]
}

Invoice Processing

{
  "webhooks": [
    {
      "id": "invoice_events",
      "url": "${WEBHOOK_URL}/api/webhooks/invoices",
      "events": [
        "invoice.created",
        "invoice.finalized",
        "invoice.paid",
        "invoice.payment_failed",
        "invoice.marked_uncollectible"
      ]
    }
  ]
}

Idempotency

Webhooks may be delivered multiple times. Ensure your handlers are idempotent:

async function handleInvoicePaid(invoice: Stripe.Invoice) {
  // Check if already processed
  const existing = await db.payments.findUnique({
    where: { stripeInvoiceId: invoice.id },
  });

  if (existing) {
    console.log(`Invoice ${invoice.id} already processed`);
    return;
  }

  // Process the invoice
  await db.payments.create({
    data: {
      stripeInvoiceId: invoice.id,
      amount: invoice.amount_paid,
      customerId: invoice.customer as string,
      processedAt: new Date(),
    },
  });
}

Testing Webhooks

Local Development

Use the Stripe CLI to forward webhooks to your local server:

stripe listen --forward-to localhost:3000/api/stripe/webhook

Trigger Test Events

stripe trigger customer.subscription.created
stripe trigger invoice.paid
stripe trigger payment_intent.succeeded

See Testing Guide for comprehensive webhook testing strategies.

Best Practices

  1. Verify Signatures — Always verify webhook signatures in production

  2. Respond Quickly — Return a 2xx response within 10 seconds; process asynchronously if needed

  3. Handle Idempotency — Webhooks may be delivered multiple times

  4. Log Events — Log all received events for debugging

  5. Use Specific Events — Subscribe only to events you need

  6. Separate Endpoints — Consider separate endpoints for different event categories

On this page