async-awaitstripe-paymentscloudflaresveltekitsupabase

SvelteKit Stripe webhook handler not completing async operation - How to ensure execution? (Issue caused by Cloudflare)


I'm encountering an issue with a Stripe webhook handler in my SvelteKit project. The handler processes a checkout session and should store the customer ID in Supabase, but it appears the operation is being terminated before completion. Here's the relevant code:

export const storeStripeCustomerId = async (session: Stripe.Checkout.Session) => {
    let customerId: string = '';
    if (typeof session.customer === 'string') {
        customerId = session.customer;
    } else if (session.customer !== null) {
        customerId = session.customer.id;
    }
    console.log("Storing Stripe customer ID", customerId, "for session", session.id);
    if (customerId) {
        // This being logged
        console.log("session.client_reference_id", session.client_reference_id);
        const {error} = await SUPABASE_SERVICE_ROLE.schema('stripe').from('user_id_table').upsert({
            id: session.client_reference_id,
            stripe_customerid: customerId,
            stripe_email: session.customer_details?.email
        });
    

        // This is not logged anymore
        console.log("Test");
        console.log(error);
      
        if (error) {
            console.error(error);
            errorReturn(400, "Supabase: " + error.message);
        }
    }
}
// /api/stripe/webhook
...
case 'checkout.session.completed': {
    const session: Stripe.Checkout.Session = event.data.object
    storeStripeCustomerId(session);
    break;
}

The problem is that the logs after the Supabase upsert operation don't appear, suggesting that the function execution is being cut off. However, I can't await it as I need to send a quick response back to Stripe.

I've verified that the environment variables for Supabase are correct and the webhook itself works properly (gets called by stripe). How can I ensure that the storeStripeCustomerId function completes its execution, even if the main handler has already responded? Additional context:

Any suggestions on how to properly handle this asynchronous operation in a SvelteKit context would be greatly appreciated.


Solution

  • Turns out Cloudflare is terminating the worker for the webhook before the async function finishes, since the webhook already returned a 200 answer. This also explains why it worked locally.

    I resolved it by using the waitUntil function from Cloudflare. This function prevents the worker from terminating prematurely, allowing asynchronous operations to complete even after sending the HTTP response (note that it's still async and not awaiting anything). Here’s how I integrated waitUntil into my webhook handler:

    // Webhook
    const processEvent = async () => {
        switch (event.type) {
            case 'entitlements.active_entitlement_summary.updated':
                await handleEntitlement(event.data.object.customer);
                console.log('Entitlements updated successfully');
                break;
            case 'checkout.session.completed':
                await storeStripeCustomerId(event.data.object);
                console.log('Customer ID stored successfully');
                break;
        }
    };
    // We cannot await processEvent() directly, because we need to return a quick response to Stripe
    if (platform && platform.context && platform.context.waitUntil) {
        // On Cloudflare, using waitUntil to ensure the Cloudflare worker isn't killed
        platform.context.waitUntil(processEvent());
    } else {
        // Locally or on platforms without waitUntil, running the async function directly
        processEvent().catch(error => {
            console.error('Failed to process event in background:', error);
        });
    }
    

    You have to extend your app configuration like this to make the platform available.

    interface Platform {
        env: {
            COUNTER: DurableObjectNamespace;
        };
        context: {
            waitUntil(promise: Promise<any>): void;
        };
        caches: CacheStorage & { default: Cache }
    }
    

    Here is the official Cloudflare documentation that talks about how to access waitUntil in Svelte.