convex-http-actions — community convex-http-actions, convexskills, community, ide skills, Claude Code, Cursor, Windsurf

v1.0.0
GitHub

About this Skill

Perfect for Full Stack Agents needing to build production-ready Convex applications with custom HTTP endpoints AI agent skills and templates for building production ready apps with Convex. Patterns for queries, mutations, cron jobs, webhooks, migrations, and more.

waynesutton waynesutton
[0]
[0]
Updated: 3/5/2026

Agent Capability Analysis

The convex-http-actions skill by waynesutton is an open-source community AI agent skill for Claude Code and other IDE workflows, helping agents execute tasks with better context, repeatability, and domain-specific guidance.

Ideal Agent Persona

Perfect for Full Stack Agents needing to build production-ready Convex applications with custom HTTP endpoints

Core Value

Empowers agents to build HTTP endpoints for webhooks, external API integrations, and custom routes in Convex applications using Convex HTTP Actions, supporting features like queries, mutations, cron jobs, and authentication protocols

Capabilities Granted for convex-http-actions

Building custom webhooks for real-time data processing
Integrating external APIs for enhanced application functionality
Creating cron jobs for scheduled task automation

! Prerequisites & Limits

  • Requires Convex platform access
  • Dependent on Convex documentation for implementation details
Labs Demo

Browser Sandbox Environment

⚡️ Ready to unleash?

Experience this Agent in a zero-setup browser environment powered by WebContainers. No installation required.

Boot Container Sandbox

convex-http-actions

AI agent skills and templates for building production ready apps with Convex. Patterns for queries, mutations, cron jobs, webhooks, migrations, and more.

SKILL.md
Readonly

Convex HTTP Actions

Build HTTP endpoints for webhooks, external API integrations, and custom routes in Convex applications.

Documentation Sources

Before implementing, do not assume; fetch the latest documentation:

Instructions

HTTP Actions Overview

HTTP actions allow you to define HTTP endpoints in Convex that can:

  • Receive webhooks from third-party services
  • Create custom API routes
  • Handle file uploads
  • Integrate with external services
  • Serve dynamic content

Basic HTTP Router Setup

typescript
1// convex/http.ts 2import { httpRouter } from "convex/server"; 3import { httpAction } from "./_generated/server"; 4 5const http = httpRouter(); 6 7// Simple GET endpoint 8http.route({ 9 path: "/health", 10 method: "GET", 11 handler: httpAction(async (ctx, request) => { 12 return new Response(JSON.stringify({ status: "ok" }), { 13 status: 200, 14 headers: { "Content-Type": "application/json" }, 15 }); 16 }), 17}); 18 19export default http;

Request Handling

typescript
1// convex/http.ts 2import { httpRouter } from "convex/server"; 3import { httpAction } from "./_generated/server"; 4 5const http = httpRouter(); 6 7// Handle JSON body 8http.route({ 9 path: "/api/data", 10 method: "POST", 11 handler: httpAction(async (ctx, request) => { 12 // Parse JSON body 13 const body = await request.json(); 14 15 // Access headers 16 const authHeader = request.headers.get("Authorization"); 17 18 // Access URL parameters 19 const url = new URL(request.url); 20 const queryParam = url.searchParams.get("filter"); 21 22 return new Response( 23 JSON.stringify({ received: body, filter: queryParam }), 24 { 25 status: 200, 26 headers: { "Content-Type": "application/json" }, 27 } 28 ); 29 }), 30}); 31 32// Handle form data 33http.route({ 34 path: "/api/form", 35 method: "POST", 36 handler: httpAction(async (ctx, request) => { 37 const formData = await request.formData(); 38 const name = formData.get("name"); 39 const email = formData.get("email"); 40 41 return new Response( 42 JSON.stringify({ name, email }), 43 { 44 status: 200, 45 headers: { "Content-Type": "application/json" }, 46 } 47 ); 48 }), 49}); 50 51// Handle raw bytes 52http.route({ 53 path: "/api/upload", 54 method: "POST", 55 handler: httpAction(async (ctx, request) => { 56 const bytes = await request.bytes(); 57 const contentType = request.headers.get("Content-Type") ?? "application/octet-stream"; 58 59 // Store in Convex storage 60 const blob = new Blob([bytes], { type: contentType }); 61 const storageId = await ctx.storage.store(blob); 62 63 return new Response( 64 JSON.stringify({ storageId }), 65 { 66 status: 200, 67 headers: { "Content-Type": "application/json" }, 68 } 69 ); 70 }), 71}); 72 73export default http;

Path Parameters

Use path prefix matching for dynamic routes:

typescript
1// convex/http.ts 2import { httpRouter } from "convex/server"; 3import { httpAction } from "./_generated/server"; 4 5const http = httpRouter(); 6 7// Match /api/users/* with pathPrefix 8http.route({ 9 pathPrefix: "/api/users/", 10 method: "GET", 11 handler: httpAction(async (ctx, request) => { 12 const url = new URL(request.url); 13 // Extract user ID from path: /api/users/123 -> "123" 14 const userId = url.pathname.replace("/api/users/", ""); 15 16 return new Response( 17 JSON.stringify({ userId }), 18 { 19 status: 200, 20 headers: { "Content-Type": "application/json" }, 21 } 22 ); 23 }), 24}); 25 26export default http;

CORS Configuration

typescript
1// convex/http.ts 2import { httpRouter } from "convex/server"; 3import { httpAction } from "./_generated/server"; 4 5const http = httpRouter(); 6 7// CORS headers helper 8const corsHeaders = { 9 "Access-Control-Allow-Origin": "*", 10 "Access-Control-Allow-Methods": "GET, POST, PUT, DELETE, OPTIONS", 11 "Access-Control-Allow-Headers": "Content-Type, Authorization", 12 "Access-Control-Max-Age": "86400", 13}; 14 15// Handle preflight requests 16http.route({ 17 path: "/api/data", 18 method: "OPTIONS", 19 handler: httpAction(async () => { 20 return new Response(null, { 21 status: 204, 22 headers: corsHeaders, 23 }); 24 }), 25}); 26 27// Actual endpoint with CORS 28http.route({ 29 path: "/api/data", 30 method: "POST", 31 handler: httpAction(async (ctx, request) => { 32 const body = await request.json(); 33 34 return new Response( 35 JSON.stringify({ success: true, data: body }), 36 { 37 status: 200, 38 headers: { 39 "Content-Type": "application/json", 40 ...corsHeaders, 41 }, 42 } 43 ); 44 }), 45}); 46 47export default http;

Webhook Handling

typescript
1// convex/http.ts 2import { httpRouter } from "convex/server"; 3import { httpAction } from "./_generated/server"; 4import { internal } from "./_generated/api"; 5 6const http = httpRouter(); 7 8// Stripe webhook 9http.route({ 10 path: "/webhooks/stripe", 11 method: "POST", 12 handler: httpAction(async (ctx, request) => { 13 const signature = request.headers.get("stripe-signature"); 14 if (!signature) { 15 return new Response("Missing signature", { status: 400 }); 16 } 17 18 const body = await request.text(); 19 20 // Verify webhook signature (in action with Node.js) 21 try { 22 await ctx.runAction(internal.stripe.verifyAndProcessWebhook, { 23 body, 24 signature, 25 }); 26 return new Response("OK", { status: 200 }); 27 } catch (error) { 28 console.error("Webhook error:", error); 29 return new Response("Webhook error", { status: 400 }); 30 } 31 }), 32}); 33 34// GitHub webhook 35http.route({ 36 path: "/webhooks/github", 37 method: "POST", 38 handler: httpAction(async (ctx, request) => { 39 const event = request.headers.get("X-GitHub-Event"); 40 const signature = request.headers.get("X-Hub-Signature-256"); 41 42 if (!signature) { 43 return new Response("Missing signature", { status: 400 }); 44 } 45 46 const body = await request.text(); 47 48 await ctx.runAction(internal.github.processWebhook, { 49 event: event ?? "unknown", 50 body, 51 signature, 52 }); 53 54 return new Response("OK", { status: 200 }); 55 }), 56}); 57 58export default http;

Webhook Signature Verification

typescript
1// convex/stripe.ts 2"use node"; 3 4import { internalAction, internalMutation } from "./_generated/server"; 5import { internal } from "./_generated/api"; 6import { v } from "convex/values"; 7import Stripe from "stripe"; 8 9const stripe = new Stripe(process.env.STRIPE_SECRET_KEY!); 10 11export const verifyAndProcessWebhook = internalAction({ 12 args: { 13 body: v.string(), 14 signature: v.string(), 15 }, 16 returns: v.null(), 17 handler: async (ctx, args) => { 18 const webhookSecret = process.env.STRIPE_WEBHOOK_SECRET!; 19 20 // Verify signature 21 const event = stripe.webhooks.constructEvent( 22 args.body, 23 args.signature, 24 webhookSecret 25 ); 26 27 // Process based on event type 28 switch (event.type) { 29 case "checkout.session.completed": 30 await ctx.runMutation(internal.payments.handleCheckoutComplete, { 31 sessionId: event.data.object.id, 32 customerId: event.data.object.customer as string, 33 }); 34 break; 35 36 case "customer.subscription.updated": 37 await ctx.runMutation(internal.subscriptions.handleUpdate, { 38 subscriptionId: event.data.object.id, 39 status: event.data.object.status, 40 }); 41 break; 42 } 43 44 return null; 45 }, 46});

Authentication in HTTP Actions

typescript
1// convex/http.ts 2import { httpRouter } from "convex/server"; 3import { httpAction } from "./_generated/server"; 4import { internal } from "./_generated/api"; 5 6const http = httpRouter(); 7 8// API key authentication 9http.route({ 10 path: "/api/protected", 11 method: "GET", 12 handler: httpAction(async (ctx, request) => { 13 const apiKey = request.headers.get("X-API-Key"); 14 15 if (!apiKey) { 16 return new Response( 17 JSON.stringify({ error: "Missing API key" }), 18 { status: 401, headers: { "Content-Type": "application/json" } } 19 ); 20 } 21 22 // Validate API key 23 const isValid = await ctx.runQuery(internal.auth.validateApiKey, { 24 apiKey, 25 }); 26 27 if (!isValid) { 28 return new Response( 29 JSON.stringify({ error: "Invalid API key" }), 30 { status: 403, headers: { "Content-Type": "application/json" } } 31 ); 32 } 33 34 // Process authenticated request 35 const data = await ctx.runQuery(internal.data.getProtectedData, {}); 36 37 return new Response( 38 JSON.stringify(data), 39 { status: 200, headers: { "Content-Type": "application/json" } } 40 ); 41 }), 42}); 43 44// Bearer token authentication 45http.route({ 46 path: "/api/user", 47 method: "GET", 48 handler: httpAction(async (ctx, request) => { 49 const authHeader = request.headers.get("Authorization"); 50 51 if (!authHeader?.startsWith("Bearer ")) { 52 return new Response( 53 JSON.stringify({ error: "Missing or invalid Authorization header" }), 54 { status: 401, headers: { "Content-Type": "application/json" } } 55 ); 56 } 57 58 const token = authHeader.slice(7); 59 60 // Validate token and get user 61 const user = await ctx.runQuery(internal.auth.validateToken, { token }); 62 63 if (!user) { 64 return new Response( 65 JSON.stringify({ error: "Invalid token" }), 66 { status: 403, headers: { "Content-Type": "application/json" } } 67 ); 68 } 69 70 return new Response( 71 JSON.stringify(user), 72 { status: 200, headers: { "Content-Type": "application/json" } } 73 ); 74 }), 75}); 76 77export default http;

Calling Mutations and Queries

typescript
1// convex/http.ts 2import { httpRouter } from "convex/server"; 3import { httpAction } from "./_generated/server"; 4import { api, internal } from "./_generated/api"; 5 6const http = httpRouter(); 7 8http.route({ 9 path: "/api/items", 10 method: "POST", 11 handler: httpAction(async (ctx, request) => { 12 const body = await request.json(); 13 14 // Call a mutation 15 const itemId = await ctx.runMutation(internal.items.create, { 16 name: body.name, 17 description: body.description, 18 }); 19 20 // Query the created item 21 const item = await ctx.runQuery(internal.items.get, { id: itemId }); 22 23 return new Response( 24 JSON.stringify(item), 25 { status: 201, headers: { "Content-Type": "application/json" } } 26 ); 27 }), 28}); 29 30http.route({ 31 path: "/api/items", 32 method: "GET", 33 handler: httpAction(async (ctx, request) => { 34 const url = new URL(request.url); 35 const limit = parseInt(url.searchParams.get("limit") ?? "10"); 36 37 const items = await ctx.runQuery(internal.items.list, { limit }); 38 39 return new Response( 40 JSON.stringify(items), 41 { status: 200, headers: { "Content-Type": "application/json" } } 42 ); 43 }), 44}); 45 46export default http;

Error Handling

typescript
1// convex/http.ts 2import { httpRouter } from "convex/server"; 3import { httpAction } from "./_generated/server"; 4 5const http = httpRouter(); 6 7// Helper for JSON responses 8function jsonResponse(data: unknown, status = 200) { 9 return new Response(JSON.stringify(data), { 10 status, 11 headers: { "Content-Type": "application/json" }, 12 }); 13} 14 15// Helper for error responses 16function errorResponse(message: string, status: number) { 17 return jsonResponse({ error: message }, status); 18} 19 20http.route({ 21 path: "/api/process", 22 method: "POST", 23 handler: httpAction(async (ctx, request) => { 24 try { 25 // Validate content type 26 const contentType = request.headers.get("Content-Type"); 27 if (!contentType?.includes("application/json")) { 28 return errorResponse("Content-Type must be application/json", 415); 29 } 30 31 // Parse body 32 let body; 33 try { 34 body = await request.json(); 35 } catch { 36 return errorResponse("Invalid JSON body", 400); 37 } 38 39 // Validate required fields 40 if (!body.data) { 41 return errorResponse("Missing required field: data", 400); 42 } 43 44 // Process request 45 const result = await ctx.runMutation(internal.process.handle, { 46 data: body.data, 47 }); 48 49 return jsonResponse({ success: true, result }, 200); 50 } catch (error) { 51 console.error("Processing error:", error); 52 return errorResponse("Internal server error", 500); 53 } 54 }), 55}); 56 57export default http;

File Downloads

typescript
1// convex/http.ts 2import { httpRouter } from "convex/server"; 3import { httpAction } from "./_generated/server"; 4import { Id } from "./_generated/dataModel"; 5 6const http = httpRouter(); 7 8http.route({ 9 pathPrefix: "/files/", 10 method: "GET", 11 handler: httpAction(async (ctx, request) => { 12 const url = new URL(request.url); 13 const fileId = url.pathname.replace("/files/", "") as Id<"_storage">; 14 15 // Get file URL from storage 16 const fileUrl = await ctx.storage.getUrl(fileId); 17 18 if (!fileUrl) { 19 return new Response("File not found", { status: 404 }); 20 } 21 22 // Redirect to the file URL 23 return Response.redirect(fileUrl, 302); 24 }), 25}); 26 27export default http;

Examples

Complete Webhook Integration

typescript
1// convex/http.ts 2import { httpRouter } from "convex/server"; 3import { httpAction } from "./_generated/server"; 4import { internal } from "./_generated/api"; 5 6const http = httpRouter(); 7 8// Clerk webhook for user sync 9http.route({ 10 path: "/webhooks/clerk", 11 method: "POST", 12 handler: httpAction(async (ctx, request) => { 13 const svixId = request.headers.get("svix-id"); 14 const svixTimestamp = request.headers.get("svix-timestamp"); 15 const svixSignature = request.headers.get("svix-signature"); 16 17 if (!svixId || !svixTimestamp || !svixSignature) { 18 return new Response("Missing Svix headers", { status: 400 }); 19 } 20 21 const body = await request.text(); 22 23 try { 24 await ctx.runAction(internal.clerk.verifyAndProcess, { 25 body, 26 svixId, 27 svixTimestamp, 28 svixSignature, 29 }); 30 return new Response("OK", { status: 200 }); 31 } catch (error) { 32 console.error("Clerk webhook error:", error); 33 return new Response("Webhook verification failed", { status: 400 }); 34 } 35 }), 36}); 37 38export default http;
typescript
1// convex/clerk.ts 2"use node"; 3 4import { internalAction, internalMutation } from "./_generated/server"; 5import { internal } from "./_generated/api"; 6import { v } from "convex/values"; 7import { Webhook } from "svix"; 8 9export const verifyAndProcess = internalAction({ 10 args: { 11 body: v.string(), 12 svixId: v.string(), 13 svixTimestamp: v.string(), 14 svixSignature: v.string(), 15 }, 16 returns: v.null(), 17 handler: async (ctx, args) => { 18 const webhookSecret = process.env.CLERK_WEBHOOK_SECRET!; 19 const wh = new Webhook(webhookSecret); 20 21 const event = wh.verify(args.body, { 22 "svix-id": args.svixId, 23 "svix-timestamp": args.svixTimestamp, 24 "svix-signature": args.svixSignature, 25 }) as { type: string; data: Record<string, unknown> }; 26 27 switch (event.type) { 28 case "user.created": 29 await ctx.runMutation(internal.users.create, { 30 clerkId: event.data.id as string, 31 email: (event.data.email_addresses as Array<{ email_address: string }>)[0]?.email_address, 32 name: `${event.data.first_name} ${event.data.last_name}`, 33 }); 34 break; 35 36 case "user.updated": 37 await ctx.runMutation(internal.users.update, { 38 clerkId: event.data.id as string, 39 email: (event.data.email_addresses as Array<{ email_address: string }>)[0]?.email_address, 40 name: `${event.data.first_name} ${event.data.last_name}`, 41 }); 42 break; 43 44 case "user.deleted": 45 await ctx.runMutation(internal.users.remove, { 46 clerkId: event.data.id as string, 47 }); 48 break; 49 } 50 51 return null; 52 }, 53});

Schema for HTTP API

typescript
1// convex/schema.ts 2import { defineSchema, defineTable } from "convex/server"; 3import { v } from "convex/values"; 4 5export default defineSchema({ 6 apiKeys: defineTable({ 7 key: v.string(), 8 userId: v.id("users"), 9 name: v.string(), 10 createdAt: v.number(), 11 lastUsedAt: v.optional(v.number()), 12 revokedAt: v.optional(v.number()), 13 }) 14 .index("by_key", ["key"]) 15 .index("by_user", ["userId"]), 16 17 webhookEvents: defineTable({ 18 source: v.string(), 19 eventType: v.string(), 20 payload: v.any(), 21 processedAt: v.number(), 22 status: v.union( 23 v.literal("success"), 24 v.literal("failed") 25 ), 26 error: v.optional(v.string()), 27 }) 28 .index("by_source", ["source"]) 29 .index("by_status", ["status"]), 30 31 users: defineTable({ 32 clerkId: v.string(), 33 email: v.string(), 34 name: v.string(), 35 }).index("by_clerk_id", ["clerkId"]), 36});

Best Practices

  • Never run npx convex deploy unless explicitly instructed
  • Never run any git commands unless explicitly instructed
  • Always validate and sanitize incoming request data
  • Use internal functions for database operations
  • Implement proper error handling with appropriate status codes
  • Add CORS headers for browser-accessible endpoints
  • Verify webhook signatures before processing
  • Log webhook events for debugging
  • Use environment variables for secrets
  • Handle timeouts gracefully

Common Pitfalls

  1. Missing CORS preflight handler - Browsers send OPTIONS requests first
  2. Not validating webhook signatures - Security vulnerability
  3. Exposing internal functions - Use internal functions from HTTP actions
  4. Forgetting Content-Type headers - Clients may not parse responses correctly
  5. Not handling request body errors - Invalid JSON will throw
  6. Blocking on long operations - Use scheduled functions for heavy processing

References

FAQ & Installation Steps

These questions and steps mirror the structured data on this page for better search understanding.

? Frequently Asked Questions

What is convex-http-actions?

Perfect for Full Stack Agents needing to build production-ready Convex applications with custom HTTP endpoints AI agent skills and templates for building production ready apps with Convex. Patterns for queries, mutations, cron jobs, webhooks, migrations, and more.

How do I install convex-http-actions?

Run the command: npx killer-skills add waynesutton/convexskills/convex-http-actions. It works with Cursor, Windsurf, VS Code, Claude Code, and 19+ other IDEs.

What are the use cases for convex-http-actions?

Key use cases include: Building custom webhooks for real-time data processing, Integrating external APIs for enhanced application functionality, Creating cron jobs for scheduled task automation.

Which IDEs are compatible with convex-http-actions?

This skill is compatible with Cursor, Windsurf, VS Code, Trae, Claude Code, OpenClaw, Aider, Codex, OpenCode, Goose, Cline, Roo Code, Kiro, Augment Code, Continue, GitHub Copilot, Sourcegraph Cody, and Amazon Q Developer. Use the Killer-Skills CLI for universal one-command installation.

Are there any limitations for convex-http-actions?

Requires Convex platform access. Dependent on Convex documentation for implementation details.

How To Install

  1. 1. Open your terminal

    Open the terminal or command line in your project directory.

  2. 2. Run the install command

    Run: npx killer-skills add waynesutton/convexskills/convex-http-actions. The CLI will automatically detect your IDE or AI agent and configure the skill.

  3. 3. Start using the skill

    The skill is now active. Your AI agent can use convex-http-actions immediately in the current project.

Related Skills

Looking for an alternative to convex-http-actions or another community skill for your workflow? Explore these related open-source skills.

View All

widget-generator

Logo of f
f

f.k.a. Awesome ChatGPT Prompts. Share, discover, and collect prompts from the community. Free and open source — self-host for your organization with complete privacy.

149.6k
0
AI

flags

Logo of vercel
vercel

flags is a Next.js feature management skill that enables developers to efficiently add or modify framework feature flags, streamlining React application development.

138.4k
0
Browser

zustand

Logo of lobehub
lobehub

The ultimate space for work and life — to find, build, and collaborate with agent teammates that grow with you. We are taking agent harness to the next level — enabling multi-agent collaboration, effortless agent team design, and introducing agents as the unit of work interaction.

72.8k
0
AI

data-fetching

Logo of lobehub
lobehub

The ultimate space for work and life — to find, build, and collaborate with agent teammates that grow with you. We are taking agent harness to the next level — enabling multi-agent collaboration, effortless agent team design, and introducing agents as the unit of work interaction.

72.8k
0
AI