Docs

HTTP functions

Answer any method on any sub-path with your own status, headers and body: webhooks that verify signatures on the raw body, OAuth callbacks, GET APIs and files, with Web Request/Response and Deno.serve.

Updated Oct 26, 2026

Every function answers /functions/v1/<name> and every path below it (/functions/v1/<name>/users/42), on every method. How the request reaches your code depends on the function's mode.

ModeYour handler getsIt returns
JSON (default)The JSON body as the event; context.http has the method, sub-path, query and headersA value, sent back as JSON with status 200
HTTPThe whole request: method, sub-path, query, headers, raw body (bytes), client IPA response: status, headers and any body (text, JSON, binary, a redirect)
any method, /name/pathsandboxCallerapp, Stripe,GitHub, browserGatewaykeys or public,WAF, rate limitsForgebody cap, policy,planYour handlerRequest in,Response out

#Make a function an HTTP function

  • Write a handler that is HTTP by itself: Python def fetch(request), JavaScript/TypeScript export default { fetch(req, ctx) {} } or Deno.serve(handler).
  • Or keep handler and turn HTTP mode on: console › Functions › your function › Who can call it › "HTTP function", sov fn deploy --http, or PUT /api/v1/fn/functions/<name>/http {"http": true}.
export default {
  async fetch(req: Request, ctx) {               // ctx.path = the sub-path, ctx.caller = who called
    const url = new URL(req.url)
    if (req.method === 'GET' && ctx.path.startsWith('/users/')) {
      return Response.json({ id: ctx.path.split('/')[2], q: url.searchParams.get('q') })
    }
    ctx.waitUntil(fetch('https://hooks.example.com/seen', { method: 'POST' }))   // finishes before the call ends
    return new Response('Not found', { status: 404 })
  },
}

Go functions in HTTP mode get the request as the event (method, path, query_string, headers, body_b64, ip) and return {"statusCode": 200, "headers": {…}, "body": "…", "isBase64Encoded": false}.

#Verify a webhook signature

Signatures are computed over the exact bytes the provider sent, so verify them on the raw body. Make the function public (console › Who can call it, or PUT …/access {"public": true}): providers call without a key. Keep the signing secret in the function's secrets.

import hashlib, hmac, os, time
from sovaria_functions import Response

def handler(request):
    parts = dict(p.split("=", 1) for p in request.headers.get("stripe-signature", "").split(",") if "=" in p)
    signed = f"{parts.get('t')}.".encode() + request.body
    expected = hmac.new(os.environ["STRIPE_WEBHOOK_SECRET"].encode(), signed, hashlib.sha256).hexdigest()
    if not hmac.compare_digest(expected, parts.get("v1", "")) or abs(time.time() - int(parts.get("t", 0))) > 300:
        return Response("bad signature", status=400)
    event = request.json()
    # … handle event["type"] …
    return Response.json({"received": True})

#Call it

const res = await sovaria.functions.fetch('api', 'users/42', { method: 'PUT', body: JSON.stringify(user),
  headers: { 'Content-Type': 'application/json' } })
if (res.status === 201) console.log(await res.json())

#What Sovaria adds and removes

  • Your Sovaria credentials (Authorization, apikey, Sovaria cookies) and the gateway's internal headers are never passed to your code. Who called is in context.caller (user id, role). A keyless call to a public function keeps its own Authorization header (a provider's bearer token).
  • Your app's cookies reach the function only on your custom domain; on the shared <ref>.sovaria.in host the browser also sends cookies for the platform domain, which never reach customer code.
  • On the shared project host, HTML (and SVG/XML) responses are sent as text/plain with a sandbox CSP, as Supabase does: the console and API live on the same site. Serve pages from web hosting (a rewrite to the function) or a custom domain.
  • A response may not set framing headers (Content-Length, Transfer-Encoding, Content-Encoding), Strict-Transport-Security or X-Sovaria-*; cookies are host-only (no Domain) and may not use Sovaria's cookie names.
  • The WAF, the function's policy (allowed origins, payload limit, rate limit), the plan's concurrency and the monthly quota apply to every call, whatever the method.
  • The gateway waits for your plan's function timeout (fn.timeout_sec, plus a small margin), so a 5-minute report on Ultra returns its result instead of a 504.
FreeProUltraTeamEnterprise
Largest request body for an HTTP function call1 MB6 MB10 MB20 MB20 MB
Function timeouts10 s1 min5 min15 min15 min
Concurrent function invocations5502005002,000

Responses are up to 10 MB. Bodies above the plan's fn.http_body_bytes get 413. A JSON function called with a body that isn't JSON gets 400 with a hint to switch to HTTP mode.

#Coming from Firebase or Supabase?

PlatformWhat this is called there
FirebaseCloud Functions HTTP functions (onRequest)
SupabaseSupabase Edge Functions Request/Response, Deno.serve
SovariaHTTP functions

Moving an existing app? Migrate from Supabase or Firebase imports users (with their passwords), data, files and rules.