How to Handle Webhook Notifications for GPT-Image2 Results
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 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, 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:
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, 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
completeCreditPackOrderto finalize the purchase - Memberships: The handler retrieves the subscription object, upserts the membership record via
upsertMembershipFromSubscription, and grants initial credits throughgrantMembershipCredits
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:
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.completedinvoice.payment_succeededcustomer.subscription.updatedcustomer.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.jsand accepts only POST requests with raw body parsing disabled. - Security: All requests require signature verification using
STRIPE_WEBHOOK_SECRETto prevent unauthorized credit manipulation. - Event Types: The system processes
checkout.session.completedfor purchases,invoice.payment_succeededfor recurring payments, and subscription lifecycle events for membership updates. - Credit Management: Helper functions
completeCreditPackOrder,upsertMembershipFromSubscription, andgrantMembershipCreditsfromapi/_lib/billing.jshandle 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.
Have a question about this repo?
These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:
curl -s "https://instagit.com/install.md" Maintain an open-source project? Get it listed too →