Invoking functions

Call your deployed functions.

Call your deployed functions.

Choose an invocation mode

Every function has an invocation contract:

  • rpc (default) preserves the existing Volcano contract. The DNS endpoint and direct API endpoint accept POST, the request body is { "payload": ... }, and Volcano authentication is required.
  • http turns the DNS endpoint into an HTTP handler. It accepts GET, HEAD, POST, PUT, PATCH, and DELETE on nested paths and passes the HTTP request shape to your function.

HTTP mode has a separate http_auth_mode:

  • volcano (default) requires a service key, or an auth-user token or permitted anon key that the function's visibility admits.
  • none skips Volcano authentication so third-party webhooks can reach the function. It is valid only when visibility is public; your function must verify the provider's signature or application credential.

Configure an existing function with the management API:

curl -X PATCH "https://api.volcano.dev/projects/$PROJECT_ID/functions/$FUNC_ID" \
  -H "Authorization: Bearer $PLATFORM_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{
    "visibility": "public",
    "invocation_mode": "http",
    "http_auth_mode": "none",
    "openapi_spec": {
      "openapi": "3.1.0",
      "info": {"title": "Payment webhook", "version": "1.0.0"},
      "paths": {"/webhooks/payments": {"post": {}}}
    }
  }'

openapi_spec is optional descriptive metadata. Volcano validates that it is an OpenAPI 3.0 or 3.1 JSON document and limits it to 256 KiB; it does not use the document to route or validate runtime requests.

Roll back HTTP mode

Invocation metadata is control-plane configuration, so switching back does not redeploy or replace the function runtime. Patch the function back to rpc; Volcano atomically restores http_auth_mode: volcano and clears openapi_spec. Moving the function off public at the same time closes anon-key access as well.

Remove any Frontend Function routes attached to the function first. While a route targets it, the function must stay public and in HTTP mode, and this update answers 409. Then patch it:

curl -X PATCH "https://api.volcano.dev/projects/$PROJECT_ID/functions/$FUNC_ID" \
  -H "Authorization: Bearer $PLATFORM_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{"visibility":"authenticated","invocation_mode":"rpc"}'

After the update, the DNS endpoint again accepts only POST / with the {"payload": ...} RPC envelope and Volcano authentication. Existing RPC functions require no migration action: a missing metadata row resolves to the same RPC/Volcano defaults.

Mount an HTTP Function on a Frontend path

A frontend can send a path prefix, such as /api/session, to a public HTTP-mode function, so your web app calls it on its own origin and can keep sessions in cookies. See Frontend Function routes.

Two ways to consume an RPC function

You can invoke a function in either of these ways:

  1. DNS endpoint (recommended): the function's invoke_url
    Geo-routed, and the only way to reach the function on its own domain.
  2. Invoke endpoint: POST https://api.volcano.dev/functions/{functionId}/invoke
    Useful when you want direct invocation on the API host.

Functions answer on their own domain, separate from the API. Never build the invocation host yourself — the endpoint differs between deployments, and a host derived from your API URL will not reach the function. Read invoke_url from the function or from GET /functions/resolve:

INVOKE_URL=$(curl -s "https://api.volcano.dev/projects/$PROJECT_ID/functions/$FUNC_ID" \
  -H "Authorization: Bearer $PLATFORM_TOKEN" | jq -r '.invoke_url')

Every example below invokes $INVOKE_URL.

Example DNS call:

curl -X POST "$INVOKE_URL" \
  -H "Authorization: Bearer $SERVICE_KEY" \
  -H "Content-Type: application/json" \
  -d '{"payload":{"action":"process","data":"value"}}'

Invocation methods by token type

Which tokens a function accepts depends on its visibility. New functions are private: only service keys and schedulers can invoke them until you set authenticated or public.

In local mode, a request with no Authorization header is treated as the local service key, so it reaches every function. Send a user's access token or an anon key to see the refusals your app will get in the cloud.

With service key (admin/background)

For admin operations, background jobs, cron, and webhooks:

curl -X POST "$INVOKE_URL" \
  -H "Authorization: Bearer $SERVICE_KEY" \
  -H "Content-Type: application/json" \
  -d '{"payload":{"action":"process","data":"value"}}'

Warning: Service keys bypass RLS. Use only in backend environments.

Function receives:

{
  "action": "process",
  "data": "value"
  // No __volcano_auth (admin operation)
}

HTTP mode request event

HTTP-mode functions receive this event instead of the RPC payload:

{
  "version": "1.0",
  "__volcano_http": true,
  "method": "POST",
  "path": "/webhooks/payments",
  "raw_query_string": "attempt=one&attempt=two",
  "headers": {
    "Content-Type": ["application/json"],
    "Provider-Signature": ["signature"]
  },
  "query": {
    "attempt": ["one", "two"]
  },
  "body": "{\"event\":\"payment.succeeded\"}",
  "is_base64_encoded": false,
  "request_context": {
    "host": "FUNCTION_ID.functions.volcano.run",
    "scheme": "https",
    "source_ip": "203.0.113.10",
    "protocol": "HTTP/1.1"
  }
}

Header and query values are arrays so repeated values are preserved. For text requests, body contains the exact UTF-8 request text. Binary requests put the base64-encoded bytes in body and set is_base64_encoded: true. HTTP request bodies are limited to 2 MiB. Volcano also rejects a request if headers, query parameters, body encoding, and trusted auth context make the final invocation event exceed 6 MiB.

__volcano_http: true is a server-owned marker that identifies this as an HTTP-ingress event, so a function can distinguish trusted request_context metadata from a caller-shaped RPC payload. Volcano removes a caller-supplied __volcano_http field from synchronous and durable RPC payloads.

In http_auth_mode: volcano, platform credential headers are removed before the event reaches the function; authenticated-user context remains available as event.__volcano_auth. In http_auth_mode: none, Authorization and provider signature headers are application input and are forwarded. Internal X-Volcano-* headers are never forwarded. Proxy-derived headers (Forwarded, X-Forwarded-For, X-Forwarded-Host, X-Forwarded-Port, X-Forwarded-Proto, and X-Real-IP) are also removed. So are lookalikes of these and of X-Volcano-* that put another character, such as _ or ., in place of -: frameworks that map headers to CGI-style variables read X_Forwarded_For as X-Forwarded-For. Other names with _ or ., such as X_Api_Key, are forwarded. Use request_context.source_ip, request_context.host, request_context.scheme, and request_context.protocol for Volcano's trusted connection metadata. A request that reaches the function through a frontend's Function route carries the visitor's address in source_ip too.

Webhook example:

import crypto from 'node:crypto';

exports.handler = async (event) => {
  const signature = event.headers['Provider-Signature']?.[0] || '';
  const rawBody = Buffer.from(event.body || '', event.is_base64_encoded ? 'base64' : 'utf8');
  const expected = crypto.createHmac('sha256', process.env.WEBHOOK_SECRET).update(rawBody).digest('hex');
  const supplied = Buffer.from(signature);
  const calculated = Buffer.from(expected);

  if (supplied.length !== calculated.length || !crypto.timingSafeEqual(supplied, calculated)) {
    return { statusCode: 401, body: 'invalid signature' };
  }

  return {
    statusCode: 202,
    headers: { 'content-type': 'application/json' },
    body: JSON.stringify({ accepted: true }),
  };
};

Reject stale provider timestamps before comparing the signature, and persist the provider event ID before applying side effects so retries are idempotent. Compare signatures with timingSafeEqual as above. Convert event.body according to event.is_base64_encoded for the exact signed bytes; do not parse and reserialize it first.

With auth user token (user context)

For user-facing operations. The function must be authenticated or public; a private function answers 404, as if it didn't exist.

curl -X POST "$INVOKE_URL" \
  -H "Authorization: Bearer $ACCESS_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{"payload":{"action":"get_profile"}}'

Function receives:

{
  "action": "get_profile",
  "__volcano_auth": {
    "user_id": "uuid",
    "email": "user@example.com",
    "project_id": "project-uuid",
    "role": "authenticated",  // or "anonymous" for anonymous users
    "access_token": "eyJhbGci..."  // the caller's access token, e.g. to forward to the Volcano SDK/API
  }
}

With anon key (public functions only)

Use this only for explicitly public functions:

curl -X POST "$INVOKE_URL" \
  -H "Authorization: Bearer $ANON_KEY" \
  -H "Content-Type: application/json" \
  -d '{"payload":{"action":"public_ping"}}'

Requirements:

  • Function visibility must be public
  • Anon key must include functions.invoke permission

Security note: Treat a public function as internet-facing. An anon key ships in browser code, a Frontend Function route forwards requests with no credential, and http + none serves its DNS endpoint without a token.

Everything on this page is about standard functions. A durable function is not invocable through any of these: it answers 404 on this endpoint and on a function URL, whatever its visibility, because it runs as an execution you start and then poll.

Function receives:

{
  "action": "public_ping"
  // No __volcano_auth for anon key invocation
}

Payload format

Send any JSON:

{
  "action": "create_post",
  "title": "My Post",
  "content": "Content here",
  "tags": ["tech", "coding"],
  "metadata": {
    "source": "web"
  }
}

Function receives this payload, plus __volcano_auth if you call it with an auth token. The top-level __volcano_auth and __volcano_http keys are reserved: Volcano removes caller-supplied values for both, so an RPC payload never carries the HTTP-ingress marker described in HTTP mode request event.

Response format

// Volcano now forwards your function response directly.
// HTTP status code, headers, and body come from the function return value.

Every invocation response says which build and region answered:

  • X-Volcano-Version: <version> in production, <env>-<version> elsewhere (example: staging-xyz)
  • X-Volcano-Region: the region your function ran in (example: us-east-1)

When Volcano dispatched to your function, it also reports:

  • X-Volcano-Proxy-Ms: milliseconds Volcano spent preparing the call, counted from the moment your request arrived until your function was dispatched. Covers authenticating the caller, validating the request, resolving the function, and quota. Does not include function execution.
  • X-Volcano-Proxy-Handler-Ms: the part of X-Volcano-Proxy-Ms spent in the invoke endpoint itself. Subtract it from X-Volcano-Proxy-Ms to see what authentication and request validation cost.
  • X-Volcano-Compute-Ms: milliseconds spent running the function, from dispatch until it returned. Does not include proxy preparation. A streamed response sends its headers before the function finishes, so there it is the time until the response started.
X-Volcano-Proxy-Ms: 9
X-Volcano-Proxy-Handler-Ms: 2
X-Volcano-Compute-Ms: 41

Here the call waited 9 ms on Volcano, 7 ms of it before the invoke endpoint was reached, and 41 ms on the function.

X-Volcano-Function-Invoked: true appears only once your function has actually run. Use it to tell your function's own 404 from Volcano's: a 404 without it means Volcano had no such function to call, so a client caching a function's id can discard that id and look the name up again. A 404 with it came from your code, and repeating the call would run your code twice. Do not read X-Volcano-Version for this — it is stamped on every response, including errors raised before your function is reached.

X-Volcano-Version, X-Volcano-Region, X-Volcano-Function-Invoked, X-Volcano-Proxy-Ms, X-Volcano-Proxy-Handler-Ms, X-Volcano-Compute-Ms, and X-Volcano-Health are set by the platform: a response header your function returns under one of those names is dropped rather than forwarded, as are Volcano's own internal headers. Every other header you return is forwarded as written.

Handling responses

Successful response

// In function
return {
  statusCode: 200,
  body: JSON.stringify({ result: 'success' }),
};

For a binary response, base64 encode body and return isBase64Encoded: true. HEAD executes the function with method: "HEAD" but Volcano suppresses the response body.

{
  "result": "success"
}

Error response

// In function
return {
  statusCode: 404,
  body: JSON.stringify({ error: 'Not found' }),
};
{
  "error": "Not found"
}

Function errors

If your function throws an error:

{
  "errorMessage": "Something went wrong"
}

Stream a response

An HTTP-mode Node.js function can send its response while it is still running: server-sent events, model tokens, progress lines. Volcano forwards each chunk as your function writes it, on the function's URL and on Frontend Function routes.

Export config = { supportsResponseStreaming: true } from the handler module and return a Response whose body is a stream:

// index.mjs
export const config = { supportsResponseStreaming: true };

export const handler = async () => {
  const encoder = new TextEncoder();
  const body = new ReadableStream({
    async start(controller) {
      for (let tick = 1; tick <= 5; tick++) {
        controller.enqueue(encoder.encode(`data: ${JSON.stringify({ tick })}\n\n`));
        await new Promise((resolve) => setTimeout(resolve, 1000));
      }
      controller.enqueue(encoder.encode('event: done\ndata: {}\n\n'));
      controller.close();
    },
  });

  return new Response(body, {
    headers: { 'content-type': 'text/event-stream', 'cache-control': 'no-cache' },
  });
};

The usual response object works too. body can be any async iterable of strings or bytes, such as an async generator or a Node.js Readable:

// index.js
exports.config = { supportsResponseStreaming: true };

exports.handler = async () => {
  async function* lines() {
    yield 'working\n';
    await new Promise((resolve) => setTimeout(resolve, 2000));
    yield 'done\n';
  }
  return { statusCode: 200, headers: { 'content-type': 'text/plain' }, body: lines() };
};

The function must use invocation_mode: http; see Choose an invocation mode. Status, headers, and cookies are sent before the first chunk, so set them all in the returned response. A handler that does not return a stream answers normally.

Read the stream with curl -N, which prints each event as it arrives. This function is public with http_auth_mode: none; in volcano auth mode, add the Authorization header as for any HTTP-mode call:

curl -N "$INVOKE_URL/events"
data: {"tick":1}

data: {"tick":2}

...

event: done
data: {}

From Node.js:

const response = await fetch(`${process.env.INVOKE_URL}/events`);
const decoder = new TextDecoder();
for await (const chunk of response.body) {
  process.stdout.write(decoder.decode(chunk, { stream: true }));
}

From Python:

import os

import requests

with requests.get(f"{os.environ['INVOKE_URL']}/events", stream=True, timeout=(5, 60)) as response:
    response.raise_for_status()
    for line in response.iter_lines(decode_unicode=True):
        if line.startswith("data: "):
            print(line.removeprefix("data: "), flush=True)

A browser can read the same events with new EventSource(url) when the function needs no Authorization header, for example behind a Frontend Function route.

Streaming limits

  • Availability on the function's URL. Check the functions.response_streaming capability in GET /capabilities. Where it is unavailable, the function's URL still answers, with the same status, headers, cookies, and body, but sends the whole response once the function finishes. A response sent whole is limited to 6 MiB, headers included, and a larger one fails the invocation. Frontend Function routes and volcano start stream either way.
  • Time limit. The whole response counts toward the function timeout: 30 s on HOBBY and 180 s on SUPERAGENT. See Plans and limits. At the limit Volcano stops the function and closes the connection without ending the response, so the client reports an incomplete transfer. End the stream with a final event, like done above, so a client can tell a finished stream from a cut one.
  • Gaps between chunks. Within the time limit, the connection stays open however long your function waits between chunks, on the function's URL and behind a Frontend Function route.
  • Errors. A function that throws before it returns a response gets a 500. One that fails after the first chunk has its connection closed the same way as at the time limit.
  • HEAD. The function runs, and Volcano drops the body.
  • Opt-in. Returning a stream without the config export fails the invocation with a 500, and the function's logs show an error that names the export. A callback-style handler, (event, context, callback), cannot opt in.
  • Python and Ruby. These runtimes return the whole response at once. Volcano sends it when the function returns.
  • RPC mode. RPC invocations, including volcano.functions.invoke in the SDKs, always return one complete JSON result. A function that returns a stream to an RPC call fails with a streamed response needs the function's invocation_mode set to http.
  • Local mode. volcano start streams Node.js functions the same way.

Accessing user context in functions

Check if user is authenticated

exports.handler = async (event) => {
  // Check if any user (anonymous or authenticated)
  if (!event.__volcano_auth) {
    return {
      statusCode: 401,
      body: JSON.stringify({ error: 'Unauthorized' }),
    };
  }

  const { user_id, email, role } = event.__volcano_auth;

  // Proceed with authenticated operation
  // All users have a user_id for data tracking
};

Differentiate between user types

exports.handler = async (event) => {
  const auth = event.__volcano_auth;

  if (!auth) {
    // No authentication - reject or handle as public
    return { statusCode: 401, body: 'Unauthorized' };
  }

  if (auth.role === 'anonymous') {
    // Guest user - maybe prompt to create account
    return {
      statusCode: 200,
      body: JSON.stringify({
        data: result,
        message: 'Create an account to save your data permanently',
      }),
    };
  }

  if (auth.role === 'authenticated') {
    // Full registered user
    return {
      statusCode: 200,
      body: JSON.stringify({ data: result }),
    };
  }
};

Available user context fields

When invoked with an auth user token, functions receive:

event.__volcano_auth = {
  user_id: 'uuid', // Unique user ID (same for anonymous → authenticated conversion)
  email: 'user@example.com', // User's email (or internal format for anonymous)
  project_id: 'uuid', // Project the user belongs to
  role: 'authenticated', // "authenticated" or "anonymous"
  access_token: 'eyJhbGci...', // The caller's access token
};

Role values:

  • "authenticated" - User signed up with email/password or OAuth
  • "anonymous" - Guest user (signed up without credentials)

Important: Both user types have a user_id - use this to track user data. When an anonymous user converts to authenticated, their user_id stays the same, preserving all their data.

Using frontend SDK

A signed-in client can invoke authenticated and public functions. Set the level in volcano-config.yaml for every function your pages call:

# volcano-config.yaml
version: 1
functions:
  - name: get-data
    visibility: authenticated
const volcano = new VolcanoAuth({...});

// Sign in first
await volcano.auth.signIn({...});

// Invoke function (access token sent automatically)
const result = await volcano.functions.invoke('get-data', {
  action: 'get_data'
});

console.log(result.status);   // HTTP status returned by function
console.log(result.version);  // X-Volcano-Version
console.log(result.data);     // Parsed response body

Error handling

StatusDescription
401 UnauthorizedMissing Authorization header, invalid token, or token for wrong project
403 ForbiddenAnon key without functions.invoke: anon key does not have functions.invoke permission
403 ForbiddenAnon key invoking an authenticated function by id: function is not public; anon keys can only invoke public functions
404 Not FoundFunction doesn't exist in your project; is private and the caller isn't a service key; or an anon key calls a non-public function by name through an SDK
503 Service UnavailableFunction still provisioning; check status and retry

An SDK reports these 404s as a not-found error, and may keep doing so for about 30 seconds after you widen a function's visibility. See Choose who can invoke it.

What's next

GuideDescription
User context in functionsComplete guide to accessing user information
Creating functionsDeploy new functions
Function logsView execution logs
Anonymous usersGuest user authentication

On this page