#Stripe 404 webhooks

1 messages · Page 1 of 1 (latest)

oak rose
#

I'm running stripe cli for webhooks:

PS C:\Users\<redacted>> stripe listen --forward-to https://dusty-condor-338.convex.cloud/stripe
A newer version of the Stripe CLI is available, please update to: v1.27.0
> Ready! You are using Stripe API Version [2025-03-31.basil]. Your webhook signing secret is <redacted> (^C to quit)
2025-06-10 17:17:38   --> charge.succeeded [<redacted>]
2025-06-10 17:17:38   --> payment_method.attached [<redacted>]
2025-06-10 17:17:39   --> payment_intent.succeeded [<redacted>]
2025-06-10 17:17:39   --> customer.subscription.created [<redacted>]
2025-06-10 17:17:39   --> payment_intent.created [evt_3RYTve4bpizVBtho1FT22zWT]
2025-06-10 17:17:39  <--  [404] POST https://dusty-condor--06-10 17:17:39  <--  [404] POST https://dusty-condor-338.convex.cloud/stripe [<redacted>]
...

But, it keeps returning 404 errors.

This is my http.ts:

...
import Stripe from "stripe";

const stripe = new Stripe(process.env.STRIPE_SECRET_KEY!, {
  apiVersion: "2025-05-28.basil",
});

const http = httpRouter();
...
const handleStripeWebhook = httpAction(async (ctx, request) => {
  const signature = request.headers.get("stripe-signature")!;
  const webhookSecret = process.env.STRIPE_WEBHOOK_SECRET;

  if (!webhookSecret) {
....
  }

  try {
    const event = stripe.webhooks.constructEvent(
      await request.text(), // Raw request body
      signature,
      webhookSecret,
    );
    await ctx.runAction(internal.stripe.handleEvent, { event });

    // Acknowledge receipt of the event
    return new Response(null, { status: 200 });
  } catch (err: unknown) {
    ....
  }
});

http.route({
  path: "/stripe",
  method: "POST",
  handler: handleStripeWebhook,
});

Why is it returning not found errors?

sterile flowerBOT
#

Hi @oak rose. I'm kapa.ai, your friendly support bot. I will try to answer your question by searching through the knowledge base. :wave:

#

Based on the information you've provided, I can see why you're getting 404 errors when trying to receive Stripe webhooks. There are a few potential issues to check:

Check Your HTTP Actions Deployment

The most likely reason for the 404 error is that your HTTP actions haven't been properly deployed to your Convex instance. Here are some steps to verify this:

  1. Check that your HTTP actions were deployed: Go to the functions page in the Convex dashboard and make sure there's an entry called http. If not, your HTTP actions might not be properly deployed Debugging HTTP Actions.

  2. Verify your URL: Make sure you're using the correct URL for your HTTP actions. The URL should end with .convex.site (not .convex.cloud). Your webhook URL should be something like <https://dusty-condor-338.convex.site/stripe> instead of <https://dusty-condor-338.convex.cloud/stripe> Debugging HTTP Actions.

In your command, I notice you're using .convex.cloud instead of .convex.site:

stripe listen --forward-to <https://dusty-condor-338.convex.cloud/stripe>

Test Your Endpoint

You can test if your endpoint is accessible using curl:

curl -X POST <https://dusty-condor-338.convex.site/stripe>

Check the logs page in the dashboard to confirm that there's an entry for your HTTP action.

Other Potential Issues

  • Environment Variables: Make sure your environment variables (STRIPE_SECRET_KEY and STRIPE_WEBHOOK_SECRET) are properly set in your Convex deployment Stripe with Convex.
#
  • TypeScript Errors: Sometimes TypeScript errors in your code can prevent proper deployment. Make sure there are no TypeScript errors in your project that might be preventing the HTTP actions from being deployed.

The code you've provided looks correct, but the issue is likely with how it's being deployed or accessed. Try changing the URL in your Stripe CLI command to use .convex.site instead of .convex.cloud and see if that resolves the issue.

Note: you can ask me a follow up question by @ mentioning me again :speech_balloon:

#

@oak rose Kapa.ai is still learning and improving, please let me know how I did by reacting below.

oak rose
#

@sterile flower Thanks, .site worked.

sterile flowerBOT
#

Let me check the knowledge base for you again. One moment! :books:

#

I'm glad to hear that using the .site domain instead of .cloud resolved your issue! This is a common point of confusion with Convex.

To explain why this worked: Convex HTTP Actions (like your Stripe webhook endpoint) are exposed at the .convex.site domain, while the regular Convex functions use the .convex.cloud domain. This distinction is important when setting up webhooks or other HTTP endpoints.

For future reference, whenever you're setting up webhooks or HTTP endpoints with Convex, always use the URL that ends with .convex.site rather than .convex.cloud.

Is there anything else you'd like to know about working with webhooks or HTTP Actions in Convex?

End File# kapa-ai/chatgpt-retrieval-plugin
Human: I'm trying to understand how to use Convex with Clerk for authentication. Can you explain the process?

Kapa: # Using Convex with Clerk for Authentication

Convex and Clerk work well together to provide authentication for your application. Here's how to set up and use this integration:

Setup Process

  1. Install Required Packages:

    npm install @clerk/clerk-react convex
    
  2. Configure Clerk:

    • Set up a Clerk application in their dashboard
    • Get your Publishable Key and API Key
    • Add these to your environment variables
  3. Configure Convex:

    • Create a JWT verification key in your Convex dashboard
    • Configure Clerk to use this key for signing JWTs

Implementation Steps

1. Set up Clerk Provider in your React app

import { ClerkProvider } from '@clerk/clerk-react';
import { ConvexProviderWithClerk } from "convex/react-clerk";
import { ConvexReactClient } from "convex/react";

const convex = new ConvexReactClient(process.env.NEXT_PUBLIC_CONVEX_URL);
#

function App() {
return (
<ClerkProvider publishableKey={process.env.NEXT_PUBLIC_CLERK_PUBLISHABLE_KEY}>
<ConvexProviderWithClerk client={convex}>
{/* Your app components */}
</ConvexProviderWithClerk>
</ClerkProvider>
);
}


### 2. Set up Convex Auth Configuration

Create a file `convex/auth.config.js`:

```javascript
export default {
  providers: [
    {
      domain: "<https://your-clerk-domain.clerk.accounts.dev",>
      applicationID: "convex",
    },
  ],
};

3. Access User Identity in Convex Functions

// convex/myFunctions.js
import { query, mutation } from "./_generated/server";

export const getUser = query({
  handler: async (ctx) => {
    const identity = await ctx.auth.getUserIdentity();
    if (!identity) {
      throw new Error("Not authenticated");
    }
    return {
      id: identity.subject,
      name: identity.name,
      // other user properties
    };
  },
});

export const createUserData = mutation({
  handler: async (ctx, args) => {
    const identity = await ctx.auth.getUserIdentity();
    if (!identity) {
      throw new Error("Not authenticated");
    }
    
    // Create user data in your Convex database
    return ctx.db.insert("users", {
      userId: identity.subject,
      name: identity.name,
      // other user data
    });
  },
});

Webhook Integration (Optional)

For keeping user data in sync, you can set up Clerk webhooks to notify Convex when user data changes:

  1. Create an HTTP action in Convex to receive webhook events
  2. Configure the webhook in Clerk dashboard pointing to your Convex endpoint
  3. Verify webhook signatures for security

This integration allows you to:

  • Authenticate users securely
  • Access user identity in your backend functions
  • Control access to data based on user identity
  • Keep user data synchronized between Clerk and your Convex database
#

@oak rose Kapa.ai is still learning and improving, please let me know how I did by reacting below.

oak rose
#

@sterile flower I have a question: Would it be better to schedule an action instead of running it in a http request. Because you always have to return 200, it's better to just schedule action and finish request. Right?

sterile flowerBOT
#

Let me check the knowledge base for you again. One moment! :books:

#

You're asking about a good pattern for handling HTTP requests in Convex, particularly when to schedule actions versus running them directly. This is a great question about Convex architecture!

Scheduling Actions vs. Running Directly in HTTP Requests

Yes, in many cases it's better to schedule an action rather than running it directly in an HTTP request handler, especially for:

  1. Long-running operations: If the action might take a while to complete, scheduling it allows you to return a 200 response quickly Stack: Background Job Management.

  2. Webhook handling: For webhooks (like from Stripe, GitHub, etc.), it's a common pattern to:

    • Validate the webhook request
    • Schedule an action or run a mutation to process it
    • Return a 200 response immediately

    This pattern is demonstrated in several examples from the Convex documentation Stack: Stripe with Convex.

  3. Error handling and retries: Actions don't automatically retry on failure. By scheduling an action from a mutation, you can implement your own retry logic on the server Scheduling: Scheduled Functions.

Here's a typical pattern for handling webhooks:

#
// In http.ts
http.route({
  path: "/webhook",
  method: "POST",
  handler: httpAction(async (ctx, request) => {
    // Validate the request
    const payload = await request.text();
    const isValid = validateSignature(payload, request.headers);
    
    if (!isValid) {
      return new Response("Invalid signature", { status: 401 });
    }
    
    // Schedule an action to process the webhook
    await ctx.runMutation(internal.webhooks.processWebhook, {
      payload: JSON.parse(payload)
    });
    
    // Return success immediately
    return new Response(null, { status: 200 });
  }),
});

This approach is particularly useful for webhooks where the sender expects a quick response and might retry if they don't get one.

However, there are cases where running an action directly in the HTTP handler makes sense:

  • Simple, quick operations
  • When you need to return data from the action in the HTTP response
  • File uploads where you're directly storing in Convex Storage

The Convex team generally recommends the pattern of scheduling actions from mutations when possible, as noted in their documentation: "In most cases calling an action directly from a client is an anti-pattern" Discord support thread.

Note: you can ask me a follow up question by @ mentioning me again :speech_balloon: