---
name: convex-http-actions
version: 1.0.0
category: 开发编程
trigger_words:
tags:
  - api
  - web
platform: coze
source: DeepseekModel
source_url: https://deepseekmodel.com/skill?id=waynesutton-convexskills-skills-convex-http-actions-skill-md
---

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