Skip to content

Stripe

Overview#

The Stripe integration connects your Cognest assistant to the Stripe Payments and Billing APIs. Create payment links, manage customers, issue invoices, handle subscriptions, process refunds, and react to real-time payment events through Stripe webhooks. Cognest verifies webhook signatures, serializes requests to avoid race conditions, and provides typed wrappers around every Stripe resource.

Prerequisites#

  1. A Stripe account with API access enabled.
  2. Generate a Secret Key from Developers > API keys in the Stripe Dashboard. Use the test mode key (sk_test_...) during development.
  3. Create a Webhook endpoint in Developers > Webhooks pointing to your Cognest webhook URL. Copy the webhook signing secret.
  4. Add the credentials to your .env file:
.env
STRIPE_SECRET_KEY=sk_test_51ABC...
STRIPE_WEBHOOK_SECRET=whsec_abc123...

Use test mode keys during development

Never use live mode keys (sk_live_...) in development or staging environments. Stripe test mode provides a complete sandbox with test card numbers, simulated webhooks, and no real charges.

Configuration#

Add the Stripe integration to your cognest.config.yaml:

cognest.config.yaml
integrations:
  stripe:
    enabled: true
    credentials:
      secret_key: ${STRIPE_SECRET_KEY}
      webhook_secret: ${STRIPE_WEBHOOK_SECRET}
    settings:
      # Stripe API version to pin (recommended for stability)
      api_version: "2024-12-18.acacia"
      # Webhook endpoint path (Cognest registers this route automatically)
      webhook_path: /webhooks/stripe
      # Events to listen for (empty = all events)
      events:
        - payment_intent.succeeded
        - payment_intent.payment_failed
        - invoice.created
        - invoice.payment_succeeded
        - customer.subscription.created
        - customer.subscription.deleted
        - charge.dispute.created
      # Automatic retry for failed webhook deliveries
      max_retries: 3
      # Currency default for new payment links
      default_currency: usd

SDK Usage#

index.ts
import { Cognest } from '@cognest/sdk'

const cognest = new Cognest()
const stripe = cognest.integration('stripe')

// Create a payment link
const link = await stripe.createPaymentLink({
  lineItems: [{ price: 'price_1ABC123', quantity: 1 }],
  metadata: { orderId: 'order_456' },
})

console.log(`Payment link: ${link.url}`)
await cognest.start()

Available Methods#

MethodParametersDescription
createPaymentLink(options){ lineItems, metadata?, afterCompletion? }Generate a Stripe Payment Link with one or more line items. Returns the shareable URL.
getCustomer(customerId)customerId: stringRetrieve a Stripe customer by ID, including email, name, and default payment method.
listPayments(options?)options?: { customerId?, status?, limit?, startingAfter? }List payment intents with optional filters. Supports pagination.
createInvoice(options){ customerId, items, daysUntilDue?, metadata? }Create and optionally finalize a draft invoice for a customer.
refund(options){ paymentIntentId, amount?, reason? }Issue a full or partial refund for a payment intent.
getBalance()(none)Retrieve the current Stripe account balance across all currencies.
createSubscription(options){ customerId, priceId, trialDays?, metadata? }Create a new subscription for a customer with an optional trial period.

Events#

Stripe events are delivered in real time via webhooks. Cognest verifies the Stripe-Signature header against your webhook secret to ensure authenticity. Only events listed in your config events array are forwarded to your handlers.

EventPayloadDescription
payment:succeeded{ paymentIntentId, amount, currency, customerId, metadata }Fired when a payment is successfully captured.
payment:failed{ paymentIntentId, amount, currency, customerId, failureReason }Fired when a payment attempt fails.
invoice:created{ invoiceId, customerId, amountDue, currency, dueDate }Fired when a new invoice is generated.
subscription:created{ subscriptionId, customerId, priceId, status, currentPeriodEnd }Fired when a new subscription starts.
subscription:canceled{ subscriptionId, customerId, canceledAt, cancelReason }Fired when a subscription is canceled.
dispute:created{ disputeId, paymentIntentId, amount, reason, status }Fired when a customer initiates a payment dispute.
events.ts
stripe.on('payment:succeeded', async (event) => {
  console.log(
    `Payment ${event.paymentIntentId} succeeded: ${event.amount / 100} ${event.currency.toUpperCase()}`
  )

  // Send a thank-you message via another integration
  const customer = await stripe.getCustomer(event.customerId)
  console.log(`Customer: ${customer.email}`)
})

stripe.on('payment:failed', async (event) => {
  console.error(
    `Payment failed for customer ${event.customerId}: ${event.failureReason}`
  )
})

stripe.on('dispute:created', async (event) => {
  console.warn(
    `Dispute ${event.disputeId} opened for ${event.amount / 100} — reason: ${event.reason}`
  )
})

stripe.on('subscription:canceled', async (event) => {
  console.log(
    `Subscription ${event.subscriptionId} canceled: ${event.cancelReason ?? 'no reason provided'}`
  )
})

Example#

A complete example that manages SaaS subscriptions, sends payment notifications via Slack, and auto-generates monthly revenue reports:

bot.ts
import { Cognest } from '@cognest/sdk'

const cognest = new Cognest()
const stripe = cognest.integration('stripe')
const slack = cognest.integration('slack')

// Notify Slack on successful payments
stripe.on('payment:succeeded', async (event) => {
  const customer = await stripe.getCustomer(event.customerId)
  const amount = (event.amount / 100).toFixed(2)

  await slack.sendMessage({
    channel: '#payments',
    text: `Payment received: $${amount} ${event.currency.toUpperCase()} from ${customer.email}`,
  })
})

// Alert on failed payments so the team can follow up
stripe.on('payment:failed', async (event) => {
  const customer = await stripe.getCustomer(event.customerId)

  await slack.sendMessage({
    channel: '#payments-alerts',
    text: `:warning: Payment failed for ${customer.email} — ${event.failureReason}. Follow up required.`,
  })
})

// Escalate disputes immediately
stripe.on('dispute:created', async (event) => {
  const amount = (event.amount / 100).toFixed(2)

  await slack.sendMessage({
    channel: '#disputes',
    text: `:rotating_light: New dispute: $${amount} — reason: ${event.reason}. Respond within 7 days.`,
  })
})

// Welcome new subscribers
stripe.on('subscription:created', async (event) => {
  const customer = await stripe.getCustomer(event.customerId)

  await slack.sendMessage({
    channel: '#new-subscribers',
    text: `New subscriber: ${customer.email} on plan ${event.priceId}`,
  })
})

// Weekly revenue report
cognest.skill('revenue-report', {
  schedule: '0 9 * * 1', // 9 AM every Monday
  handler: async () => {
    const balance = await stripe.getBalance()
    const payments = await stripe.listPayments({
      status: 'succeeded',
      limit: 100,
    })

    const weekTotal = payments.data
      .filter((p) => {
        const age = Date.now() - p.created * 1000
        return age < 7 * 24 * 60 * 60 * 1000
      })
      .reduce((sum, p) => sum + p.amount, 0)

    const availableBalance = balance.available
      .map((b) => `${(b.amount / 100).toFixed(2)} ${b.currency.toUpperCase()}`)
      .join(', ')

    await slack.sendMessage({
      channel: '#revenue',
      text: [
        ':chart_with_upwards_trend: *Weekly Revenue Report*',
        `Payments this week: $${(weekTotal / 100).toFixed(2)}`,
        `Available balance: ${availableBalance}`,
        `Total transactions: ${payments.data.length}`,
      ].join('\n'),
    })
  },
})

// Create a checkout skill that can be invoked by other skills
cognest.skill('create-checkout', {
  handler: async ({ params }) => {
    const { priceId, quantity = 1, customerEmail } = params

    const link = await stripe.createPaymentLink({
      lineItems: [{ price: priceId, quantity }],
      metadata: { source: 'cognest-bot', customerEmail },
      afterCompletion: {
        type: 'redirect',
        url: 'https://app.example.com/thank-you',
      },
    })

    return { url: link.url, expiresAt: link.expiresAt }
  },
})

await cognest.start()
console.log('Stripe payment automation is running...')

Troubleshooting#

Common Issues

Webhook signature verification failed: Ensure STRIPE_WEBHOOK_SECRET matches the signing secret from the Stripe Dashboard (starts with whsec_). If you're using the Stripe CLI for local testing, use the CLI-provided secret, not the dashboard one. "No such customer" errors: Customer IDs from test mode (cus_test_...) won't work in live mode and vice versa. Ensure your STRIPE_SECRET_KEY matches the mode you're testing. Webhook events not arriving: Check that your webhook_path matches the URL configured in Stripe. For local development, use the Stripe CLI: stripe listen --forward-to localhost:3000/webhooks/stripe. Idempotency errors on retries: Cognest automatically adds idempotency keys to create operations. If you see duplicate creation errors, check that max_retries is not set too high and that your handlers are idempotent.

Local Development with Stripe CLI

Install the Stripe CLI and run "stripe listen --forward-to localhost:3000/webhooks/stripe" to forward webhook events to your local development server. The CLI outputs a webhook signing secret that you should use as STRIPE_WEBHOOK_SECRET during local development.