Skip to content

Lambda-to-Lambda Invocation

VentureKit uses a single invoke() function for all Lambda-to-Lambda calls:

  • invoke('route/path', opts) — Call a route handler (builds API Gateway V2 event, skips API Gateway)
  • invoke({ function: 'name' }, opts) — Call a standalone function with a raw payload
import { invoke } from '@venturekit/runtime';
// Call a route handler (API GW event format)
const result = await invoke<{ id: string; name: string }>('users/{id}', {
method: 'GET',
pathParams: { id: '42' },
});
console.log(result.data); // { id: '42', name: 'Alice' }
// Call a standalone function (src/functions/process-orders.ts)
const job = await invoke<{ jobId: string }>(
{ function: 'process-orders' },
{ payload: { orderId: 'abc', items: ['widget'] } },
);
console.log(job.data.jobId);
  1. Resolves the Lambda function name from the route path (myapp-users-{id})
  2. Builds a full API Gateway V2 event payload (same as external HTTP requests)
  3. Calls Lambda directly via AWS SDK InvokeCommandno API Gateway hop
  4. Propagates trace context, tenant ID, user ID, and an x-venturekit-source: internal header
  5. Parses the response back into a typed result

Your handler receives an identical event whether called externally (via API Gateway) or internally (via invoke()). No code changes needed.

Function invocation (invoke({ function: 'name' }, ...))

Section titled “Function invocation (invoke({ function: 'name' }, ...))”
  1. Resolves the Lambda name: {project}-{stage}-fn-{name} (e.g. myapp-dev-fn-process-orders)
  2. Sends the raw payload (JSON-serialized) — no API Gateway event wrapping
  3. Returns the function’s response as typed data

You only use the short name as it appears in your project (e.g. 'process-orders'). VentureKit adds the project prefix and stage automatically.

import { invoke } from '@venturekit/runtime';
// GET with query params
const users = await invoke<User[]>('users/list', {
method: 'GET',
queryParams: { page: '1', limit: '20' },
});
// POST with body
const created = await invoke<User>('users/create', {
body: { name: 'Alice', email: 'alice@example.com' },
});
// With path parameters
const user = await invoke<User>('users/{id}', {
method: 'GET',
pathParams: { id: '42' },
});
// Fire-and-forget (async invocation)
await invoke('notifications/send', {
mode: 'async',
body: { userId: '42', message: 'Hello!' },
});
// Propagate context
await invoke('orders/create', {
body: orderData,
trace: ctx.trace,
tenantId: ctx.tenant?.id,
userId: ctx.user?.sub,
});
route: 'users/list' → Lambda: '{project}-users-list'
route: 'tasks/{id}' → Lambda: '{project}-tasks-{id}'

Call standalone functions in src/functions/ by their short name:

import { invoke } from '@venturekit/runtime';
// Sync: call and wait for result
const result = await invoke<{ jobId: string }>(
{ function: 'process-orders' },
{ payload: { orderId: 'abc-123', items: [...] } },
);
console.log(result.data.jobId);
// Async: fire-and-forget
await invoke(
{ function: 'send-email' },
{ payload: { to: 'user@example.com', template: 'welcome' }, mode: 'async' },
);
// With trace propagation
await invoke(
{ function: 'audit-log' },
{ payload: { action: 'order.created', data: order }, trace: ctx.trace },
);
function: 'process-orders' → Lambda: '{project}-{stage}-fn-process-orders'
function: 'order-create-order' → Lambda: '{project}-{stage}-fn-order-create-order'

The project and stage are read from VENTURE_PROJECT_NAME and VENTURE_STAGE environment variables (set automatically during deployment and local dev).

Route invocations include x-venturekit-source: internal and a unique x-venturekit-invoke-id header:

import { handler } from '@venturekit/runtime';
export const main = handler({
fn: async (body, ctx) => {
const isInternal = ctx.rawEvent.headers?.['x-venturekit-source'] === 'internal';
if (isInternal) {
// Called from another Lambda via invoke() — skip rate limiting, etc.
}
return { data: { source: isInternal ? 'internal' : 'external' } };
},
});

For full control, use the lower-level createInvoker() API:

import { createInvoker, createLambdaInvoker, defaultFunctionNameResolver } from '@venturekit/runtime';
const lambdaInvoker = await createLambdaInvoker({ region: 'eu-west-1' });
const invoker = createInvoker({
invoker: lambdaInvoker,
resolver: defaultFunctionNameResolver('my-custom-prefix'),
});
const result = await invoker('users/get');

Lambda functions need lambda:InvokeFunction permission to call other functions. VentureKit’s CDK stack grants this automatically for functions in the same stack.