Skip to main content

Custom Routes

Custom routes let you register HTTP endpoints scoped to your workspace. Use them to receive webhooks from external systems, expose custom APIs, or serve data to frontend integrations.

Registering a route

Use sdk.registerRoute() inside your module's register function.

Basic example

import { ConvoticModule } from "@convotic/block-sdk";

const module: ConvoticModule = {
name: "erp-webhook",
version: "1.0.0",

register(sdk) {
sdk.registerRoute({
method: "POST",
path: "/webhook/erp",
handler: async (req, ctx) => {
const payload = req.body;

// Look up the contact by external ID
const contact = await ctx.contacts.findByExternalId(
payload.customer_id
);

if (contact) {
// Update the contact with order data from the ERP
await ctx.contacts.update(contact.id, {
attributes: {
last_erp_order_id: payload.order_id,
last_erp_order_status: payload.status,
},
});
}

return { status: 200, body: { ok: true } };
},
});
},
};

export default module;

The route will be available at:

POST https://api.convotic.com/v1/modules/erp-webhook/webhook/erp

Route handler signature

sdk.registerRoute({
method: "GET" | "POST" | "PUT" | "PATCH" | "DELETE",
path: string,
middleware?: Middleware[],
handler: (req: RouteRequest, ctx: ConvoticContext) => Promise<RouteResponse>,
});

RouteRequest

PropertyTypeDescription
bodyanyParsed request body (JSON).
queryRecord<string, string>Query string parameters.
paramsRecord<string, string>URL path parameters.
headersRecord<string, string>Request headers.

ConvoticContext

PropertyDescription
ctx.contactsContact CRUD operations.
ctx.conversationsConversation CRUD operations.
ctx.messagesSend messages to contacts.
ctx.workflowsTrigger workflows programmatically.
ctx.storageKey-value storage scoped to the module.
ctx.loggerStructured logger.

RouteResponse

{
status: number;
body?: any;
headers?: Record<string, string>;
}

Path parameters

Define dynamic path segments with :param syntax:

sdk.registerRoute({
method: "GET",
path: "/orders/:orderId",
handler: async (req, ctx) => {
const { orderId } = req.params;
const order = await ctx.storage.get(`order:${orderId}`);

if (!order) {
return { status: 404, body: { error: "Order not found" } };
}

return { status: 200, body: order };
},
});

Authentication

By default, custom routes require a valid X-API-Key header (the same key used for the Convotic API). To accept unauthenticated requests (e.g., for external webhooks), set auth: false:

sdk.registerRoute({
method: "POST",
path: "/webhook/external",
auth: false,
handler: async (req, ctx) => {
// Verify the webhook signature yourself
const isValid = verifySignature(req.headers, req.body);
if (!isValid) {
return { status: 401, body: { error: "Invalid signature" } };
}

// Process the event
await ctx.workflows.trigger("wf_abc123", {
payload: req.body,
});

return { status: 200, body: { ok: true } };
},
});
warning

When disabling authentication, always implement your own verification logic to prevent unauthorized access.