# How to Handle Webhook Notifications for GPT-Image2 Results

> Learn to handle GPT-Image2 webhook notifications for Stripe payment events. This guide explains using Next.js API routes for credit-pack and subscription management.

- Repository: [苍何/awesome-gpt-image-2](https://github.com/freestylefly/awesome-gpt-image-2)
- Tags: how-to-guide
- Published: 2026-09-09

---

**GPT-Image2 processes Stripe webhook notifications through a Next.js API route at `/api/billing/webhook` to handle asynchronous payment events including credit-pack purchases and membership subscriptions.**

The `freestylefly/awesome-gpt-image-2` repository implements a secure webhook handling system for processing GPT-Image2 payment results via Stripe. When users purchase credits or subscribe to memberships, Stripe sends asynchronous notifications that must be verified and processed to update user balances. The implementation relies on signature verification and event-specific handlers located in [`api/billing/webhook.js`](https://github.com/freestylefly/awesome-gpt-image-2/blob/main/api/billing/webhook.js) to ensure transactional integrity.

## Webhook Endpoint Architecture

The webhook endpoint is implemented as a Next.js API route that exclusively accepts POST requests. Located at [`api/billing/webhook.js`](https://github.com/freestylefly/awesome-gpt-image-2/blob/main/api/billing/webhook.js), the handler first validates that Supabase and Stripe clients are properly configured and that the `STRIPE_WEBHOOK_SECRET` environment variable is present (lines 4-12 of the handler).

The endpoint disables Next.js body parsing to preserve the raw request payload required for Stripe signature verification:

```javascript
export const config = { api: { bodyParser: false } };

export default async function handler(req, res) {
  if (req.method !== 'POST') return res.status(405).end();
  // ... signature verification and event processing
}

```

## Security Verification and Event Validation

Before processing any GPT-Image2 webhook notification, the system verifies the cryptographic signature to prevent spoofing attacks. The handler reads the raw request body using `readRawBody` and validates the `stripe-signature` header using `stripe.webhooks.constructEvent` (lines 19-23).

If signature verification fails, the endpoint immediately returns a **400 status** with the error code `INVALID_WEBHOOK_SIGNATURE`. This security layer ensures that only legitimate Stripe events trigger credit updates in your database.

## Processing Payment Events

The webhook handler dispatches incoming events to specific processors based on the `event.type` property. According to the source code in [`api/billing/webhook.js`](https://github.com/freestylefly/awesome-gpt-image-2/blob/main/api/billing/webhook.js), the system handles three primary event categories:

### Checkout Session Completion

The `checkout.session.completed` event handles both one-time credit-pack purchases and recurring membership subscriptions. The implementation distinguishes between these transaction types:

- **Credit packs**: The system calls `completeCreditPackOrder` to finalize the purchase
- **Memberships**: The handler retrieves the subscription object, upserts the membership record via `upsertMembershipFromSubscription`, and grants initial credits through `grantMembershipCredits`

In both cases, the corresponding row in the `payment_orders` table is marked as `completed` (lines 21-73).

### Invoice Payment Success

When Stripe processes recurring subscription payments, the `invoice.payment_succeeded` event triggers credit replenishment. The handler specifically ignores the initial `subscription_create` invoice to avoid double-crediting users, then calls `grantMembershipCredits` to add credits to the user's account (lines 76-98).

### Subscription Lifecycle Updates

The webhook listens for `customer.subscription.updated` and `customer.subscription.deleted` events to synchronize membership status in the database. These events refresh local membership data by re-upserting the subscription details from Stripe (lines 100-102).

## Implementation Example

The following minimal implementation demonstrates the core logic for handling GPT-Image2 webhook notifications, including signature verification and event dispatch:

```javascript
import { getSupabaseAdminClient } from '@/api/_lib/supabase';
import { getStripeClient, readRawBody } from '@/api/_lib/billing';

export const config = { api: { bodyParser: false } };

export default async function handler(req, res) {
  if (req.method !== 'POST') return res.status(405).end();

  const stripe = getStripeClient();
  const rawBody = await readRawBody(req);
  const sig = req.headers['stripe-signature'];
  
  const event = stripe.webhooks.constructEvent(
    rawBody,
    sig,
    process.env.STRIPE_WEBHOOK_SECRET
  );

  const supabase = getSupabaseAdminClient();

  switch (event.type) {
    case 'checkout.session.completed': {
      const session = event.data.object;
      if (session.mode === 'payment') {
        await completeCreditPackOrder(supabase, session);
      } else if (session.mode === 'subscription') {
        const subscription = await stripe.subscriptions.retrieve(session.subscription);
        await upsertMembershipFromSubscription(supabase, subscription);
        await grantMembershipCredits(supabase, {
          userId: subscription.metadata.userId,
          planId: subscription.metadata.productId,
          source: 'stripe_membership_initial'
        });
      }
      break;
    }
    case 'invoice.payment_succeeded': {
      if (event.data.object.billing_reason === 'subscription_create') break;
      await handleInvoicePaid(supabase, stripe, event.data.object);
      break;
    }
    case 'customer.subscription.updated':
    case 'customer.subscription.deleted': {
      await upsertMembershipFromSubscription(supabase, event.data.object);
      break;
    }
  }

  return res.json({ ok: true, received: true });
}

```

## Stripe Dashboard Configuration

To receive webhook notifications for GPT-Image2 results, configure the following endpoint in your Stripe Dashboard:

**Webhook URL:** `https://gpt-image2.canghe.ai/api/billing/webhook`

Subscribe to the following event types:
- `checkout.session.completed`
- `invoice.payment_succeeded`
- `customer.subscription.updated`
- `customer.subscription.deleted`

After successful processing, the endpoint returns `{ ok: true, received: true }`. If internal processing fails, the system returns a **500 status** with the error code `WEBHOOK_PROCESSING_FAILED` and logs the error for debugging.

## Summary

- **Endpoint Location**: The webhook handler resides at [`api/billing/webhook.js`](https://github.com/freestylefly/awesome-gpt-image-2/blob/main/api/billing/webhook.js) and accepts only POST requests with raw body parsing disabled.
- **Security**: All requests require signature verification using `STRIPE_WEBHOOK_SECRET` to prevent unauthorized credit manipulation.
- **Event Types**: The system processes `checkout.session.completed` for purchases, `invoice.payment_succeeded` for recurring payments, and subscription lifecycle events for membership updates.
- **Credit Management**: Helper functions `completeCreditPackOrder`, `upsertMembershipFromSubscription`, and `grantMembershipCredits` from [`api/_lib/billing.js`](https://github.com/freestylefly/awesome-gpt-image-2/blob/main/api/_lib/billing.js) handle database updates.
- **Response Codes**: Return 200/OK for success, 400 for invalid signatures, and 500 for processing failures.

## Frequently Asked Questions

### What happens if the webhook signature verification fails?

If the `stripe-signature` header cannot be verified using `stripe.webhooks.constructEvent`, the handler immediately returns a 400 Bad Request response with the error code `INVALID_WEBHOOK_SIGNATURE`. This prevents malicious actors from injecting fake payment confirmations into the system.

### How does GPT-Image2 distinguish between credit packs and membership subscriptions?

The handler checks the `session.mode` property within the `checkout.session.completed` event. When `mode` equals `"payment"`, it processes a one-time credit pack purchase via `completeCreditPackOrder`. When `mode` equals `"subscription"`, it retrieves the subscription object and initializes membership records through `upsertMembershipFromSubscription`.

### Why does the invoice payment handler ignore subscription_create events?

The `invoice.payment_succeeded` handler specifically checks the `billing_reason` field and skips processing when it equals `subscription_create`. This prevents double-crediting users because initial membership credits are already granted during the `checkout.session.completed` event when the subscription is first established.

### Which environment variables are required for webhook processing?

The webhook handler requires `STRIPE_WEBHOOK_SECRET` for signature verification, along with configured Supabase and Stripe credentials. The system validates these configurations at the start of the request handler (lines 4-12) and rejects requests if any required component is missing.