{
    "name": "convex-http-actions",
    "version": "1.0.0",
    "description": "External API integration and webhook handling including HTTP endpoint routing, request/response handling, authentication, CORS configuration, and webhook signature validation",
    "system_prompt": "name convex-http-actions displayName Convex HTTP Actions description External API integration and webhook handling including HTTP endpoint routing, request/response handling, authentication, CORS configuration, and webhook signature validation version 1.0.0 author Convex tags [\"convex\",\"http\",\"actions\",\"webhooks\",\"api\",\"endpoints\"] 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: Primary: https://docs.convex.dev/functions/http-actions Actions Overview: https://docs.convex.dev/functions/actions Authentication: https://docs.convex.dev/auth For broader context: https://docs.convex.dev/llms.txt 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 // convex/http.ts import { httpRouter } from \"convex/server\" ; import { httpAction } from \"./_generated/server\" ; const http = httpRouter (); // Simple GET endpoint http. route ({ path : \"/health\" , method : \"GET\" , handler : httpAction ( async (ctx, request) => { return new Response ( JSON . stringify ({ status : \"ok\" }), { status : 200 , headers : { \"Content-Type\" : \"application/json\" }, }); }), }); export default http; Request Handling // convex/http.ts import { httpRouter } from \"convex/server\" ; import { httpAction } from \"./_generated/server\" ; const http = httpRouter (); // Handle JSON body http. route ({ path : \"/api/data\" , method : \"POST\" , handler : httpAction ( async (ctx, request) => { // Parse JSON body const body = await request. json (); // Access headers const authHeader = request. headers . get ( \"Authorization\" ); // Access URL parameters const url = new URL (request. url ); const queryParam = url. searchParams . get ( \"filter\" ); return new Response ( JSON . stringify ({ received : body, filter : queryParam }), { status : 200 , headers : { \"Content-Type\" : \"application/json\" }, } ); }), }); // Handle form data http. route ({ path : \"/api/form\" , method : \"POST\" , handler : httpAction ( async (ctx, request) => { const formData = await request. formData (); const name = formData. get ( \"name\" ); const email = formData. get ( \"email\" ); return new Response ( JSON . stringify ({ name, email }), { status : 200 , headers : { \"Content-Type\" : \"application/json\" }, } ); }), }); // Handle raw bytes http. route ({ path : \"/api/upload\" , method : \"POST\" , handler : httpAction ( async (ctx, request) => { const bytes = await request. bytes (); const contentType = request. headers . get ( \"Content-Type\" ) ?? \"application/octet-stream\" ; // Store in Convex storage const blob = new Blob ([bytes], { type : contentType }); const storageId = await ctx. storage . store (blob); return new Response ( JSON . stringify ({ storageId }), { status : 200 , headers : { \"Content-Type\" : \"application/json\" }, } ); }), }); export default http; Path Parameters Use path prefix matching for dynamic routes: // convex/http.ts import { httpRouter } from \"convex/server\" ; import { httpAction } from \"./_generated/server\" ; const http = httpRouter (); // Match /api/users/* with pathPrefix http. route ({ pathPrefix : \"/api/users/\" , method : \"GET\" , handler : httpAction ( async (ctx, request) => { const url = new URL (request. url ); // Extract user ID from path: /api/users/123 -> \"123\" const userId = url. pathname . replace ( \"/api/users/\" , \"\" ); return new Response ( JSON . stringify ({ userId }), { status : 200 , headers : { \"Content-Type\" : \"application/json\" }, } ); }), }); export default http; CORS Configuration // convex/http.ts import { httpRouter } from \"convex/server\" ; import { httpAction } from \"./_generated/server\" ; const http = httpRouter (); // CORS headers helper const corsHeaders = { \"Access-Control-Allow-Origin\" : \"*\" , \"Access-Control-Allow-Methods\" : \"GET, POST, PUT, DELETE, OPTIONS\" , \"Access-Control-Allow-Headers\" : \"Content-Type, Authorization\" , \"Access-Control-Max-Age\" : \"86400\" , }; // Handle preflight requests http. route ({ path : \"/api/data\" , method : \"OPTIONS\" , handler : httpAction ( async () => { return new Response ( null , { status : 204 , headers : corsHeaders, }); }), }); // Actual endpoint with CORS http. route ({ path : \"/api/data\" , method : \"POST\" , handler : httpAction ( async (ctx, request) => { const body = await request. json (); return new Response ( JSON . stringify ({ success : true , data : body }), { status : 200 , headers : { \"Content-Type\" : \"application/json\" , ...corsHeaders, }, } ); }), }); export default http; Webhook Handling // convex/http.ts import { httpRouter } from \"convex/server\" ; import { httpAction } from \"./_generated/server\" ; import { internal } from \"./_generated/api\" ; const http = httpRouter (); // Stripe webhook http. route ({ path : \"/webhooks/stripe\" , method : \"POST\" , handler : httpAction ( async (ctx, request) => { const signature = request. headers . get ( \"stripe-signature\" ); if (!signature) { return new Response ( \"Missing signature\" , { status : 400 }); } const body = await request. text (); // Verify webhook signature (in action with Node.js) try { await ctx. runAction (internal. stripe . verifyAndProcessWebhook , { body, signature, }); return new Response ( \"OK\" , { status : 200 }); } catch (error) { console . error ( \"Webhook error:\" , error); return new Response ( \"Webhook error\" , { status : 400 }); } }), }); // GitHub webhook http. route ({ path : \"/webhooks/github\" , method : \"POST\" , handler : httpAction ( async (ctx, request) => { const event = request. headers . get ( \"X-GitHub-Event\" ); const signature = request. headers . get ( \"X-Hub-Signature-256\" ); if (!signature) { return new Response ( \"Missing signature\" , { status : 400 }); } const body = await request. text (); await ctx. runAction (internal. github . processWebhook , { event : event ?? \"unknown\" , body, signature, }); return new Response ( \"OK\" , { status : 200 }); }), }); export default http; Webhook Signature Verification // convex/stripe.ts \"use node\" ; import { internalAction, internalMutation } from \"./_generated/server\" ; import { internal } from \"./_generated/api\" ; import { v } from \"convex/values\" ; import Stripe from \"stripe\" ; const stripe = new Stripe (process. env . STRIPE_SECRET_KEY !); export const verifyAndProcessWebhook = internalAction ({ args : { body : v. string (), signature : v. string (), }, returns : v. null (), handler : async (ctx, args) => { const webhookSecret = process. env . STRIPE_WEBHOOK_SECRET !; // Verify signature const event = stripe. webhooks . constructEvent ( args. body , args. signature , webhookSecret ); // Process based on event type switch (event. type ) { case \"checkout.session.completed\" : await ctx. runMutation (internal. payments . handleCheckoutComplete , { sessionId : event. data . object . id , customerId : event. data . object . customer as string , }); break ; case \"customer.subscription.updated\" : await ctx. runMutation (internal. subscriptions . handleUpdate , { subscriptionId : event. data . object . id , status : event. data . object . status , }); break ; } return null ; }, }); Authentication in HTTP Actions // convex/http.ts import { httpRouter } from \"convex/server\" ; import { httpAction } from \"./_generated/server\" ; import { internal } from \"./_generated/api\" ; const http = httpRouter (); // API key authentication http. route ({ path : \"/api/protected\" , method : \"GET\" , handler : httpAction ( async (ctx, request) => { const apiKey = request. headers . get ( \"X-API-Key\" ); if (!apiKey) { return new Response ( JSON . stringify ({ error : \"Missing API key\" }), { status : 401 , headers : { \"Content-Type\" : \"application/json\" } } ); } // Validate API key const isValid = await ctx. runQuery (internal. auth . validateApiKey , { apiKey, }); if (!isValid) { return new Response ( JSON . stringify ({ error : \"Invalid API key\" }), { status : 403 , headers : { \"Content-Type\" : \"application/json\" } } ); } // Process authenticated request const data = await ctx. runQuery (internal. data . getProtectedData , {}); return new Response ( JSON . stringify (data), { status : 200 , headers : { \"Content-Type\" : \"application/json\" } } ); }), }); // Bearer token authentication http. route ({ path : \"/api/user\" , method : \"GET\" , handler : httpAction ( async (ctx, request) => { const authHeader = request. headers . get ( \"Authorization\" ); if (!authHeader?. startsWith ( \"Bearer \" )) { return new Response ( JSON . stringify ({ error : \"Missing or invalid Authorization header\" }), { status : 401 , headers : { \"Content-Type\" : \"application/json\" } } ); } const token = authHeader. slice ( 7 ); // Validate token and get user const user = await ctx. runQuery (internal. auth . validateToken , { token }); if (!user) { return new Response ( JSON . stringify ({ error : \"Invalid token\" }), { status : 403 , headers : { \"Content-Type\" : \"application/json\" } } ); } return new Response ( JSON . stringify (user), { status : 200 , headers : { \"Content-Type\" : \"application/json\" } } ); }), }); export default http; Calling Mutations and Queries // convex/http.ts import { httpRouter } from \"convex/server\" ; import { httpAction } from \"./_generated/server\" ; import { api, internal } from \"./_generated/api\" ; const http = httpRouter (); http. route ({ path : \"/api/items\" , method : \"POST\" , handler : httpAction ( async (ctx, request) => { const body = await request. json (); // Call a mutation const itemId = await ctx. runMutation (internal. items . create , { name : body. name , description : body. description , }); // Query the created item",
    "model_config": {
        "provider": "deepseek",
        "model": "deepseek-chat",
        "temperature": 0.7,
        "max_tokens": 4096,
        "top_p": 0.9
    },
    "trigger_words": [],
    "source": "DeepseekModel",
    "source_url": "https://deepseekmodel.com/skill?id=waynesutton-convexskills-skills-convex-http-actions-skill-md"
}