Skip to main content

Backend SDK Integration

The Backend SDK (@qefro-ai/backend / qefro-backend-sdk) runs in your backend. You register handlers; Qefro discovers and invokes them over a signed webhook (POST /qefro).

Full walkthrough: Register SDK Business Tools.
Protocol reference: SDK Framework.

When to use the SDK

  • Customer lookup and authorization (OTP, login, session reuse).
  • Multi-step challenge / resume flows.
  • Business rules that do not map to a single REST call.
  • You want handlers in code with type-safe tooling.

Project setup

TypeScript

npm install @qefro-ai/backend
export QEFRO_SIGNING_SECRET="your-signing-secret"

Rust

cargo add qefro-backend-sdk

Framework lifecycle

  1. new Qefro({ signingSecret }) — Verify incoming signatures.
  2. app.customer({ lookup, authorize }) — Optional Customer Provider.
  3. app.tool(definition, handler) — Register Business Tool handlers.
  4. app.listen({ port, path: '/qefro' }) — HTTP server for protocol messages.

Register a tool

app.tool(
{
name: 'order_status_check',
description: 'Look up order by ID when customer provides order_id.',
auth: 'none',
input_schema: {
type: 'object',
properties: { order_id: { type: 'string' } },
required: ['order_id'],
},
},
async (ctx) => {
const orderId = String(ctx.parameters.order_id).toUpperCase();
return orderService.getStatus(orderId);
},
);

Tool definition fields

FieldPurpose
nameStable id — synced as sdk_handler_name
descriptionLLM-facing summary
input_schemaJSON Schema for parameters
authnone | optional | required
authentication_methodse.g. email_otp — Sync may set organization_challenge
lookup{ required: ['email'] } — runtime resolves before invoke
permissionsEnforced in your handler
timeoutHint (seconds)

Customer Provider

app.customer({
async lookup(ctx) {
// Resolve customer from identity attributes Qefro already collected
const email = ctx.identity?.email ?? ctx.parameters?.email;
return directory.findByEmail(email);
},
async authorize(ctx) {
const customer = ctx.customer;
if (!customer) return { kind: 'not_found' };

if (!ctx.response) {
await otp.send(customer.email);
return {
kind: 'challenge',
challenge: {
type: 'email_otp',
message: 'Enter the 6-digit code sent to your email.',
destination_hint: maskEmail(customer.email),
},
};
}

if (!(await otp.verify(customer.email, ctx.response))) {
return { kind: 'denied' };
}

return {
kind: 'success',
customer,
auth: {
type: 'bearer_token',
access_token: await tokens.issue(customer.id),
expires_in: 900,
},
};
},
});

Authorize outcomes: success, challenge, denied, not_found.

See Challenge / Resume and Authentication.

Protocol messages

TypeDirectionHandler
pingQefro → youReturns pong
tools.listQefro → youReturns registered tools
tool.invokeQefro → youRuns handler
tool.resumeQefro → youContinues after challenge

Example tool.invoke payload (simplified)

{
"protocol_version": "1",
"type": "tool.invoke",
"request_id": "req-uuid",
"conversation_id": "conv-uuid",
"channel": "widget",
"tool": "my_orders_list",
"parameters": { "limit": 5 },
"identity": {
"email": "[email protected]",
"channel": "widget"
}
}

Signed headers: X-Qefro-Protocol, X-Qefro-Timestamp, X-Qefro-Signature (v1=<hmac>).

Identity in SDK webhooks

The SDK receives identity attributes, not raw end-user JWT secrets:

  • email, phone, customer_id, user_id
  • channel (widget, whatsapp, portal, …)
  • Verification / auth level metadata

For Widget REST-style forwarding of JWTs, use REST tools with END_USER_IDENTITY.
For SDK, use lookup.required + Customer Provider. See Identity resolution.

SDK Connection and Sync Tools

SDK Connections link Qefro to your @qefro-ai/backend webhook. Sync Tools discovers handlers via tools.list and registers them as workspace Business Tools.

FieldPurpose
NameAdmin label
Webhook URLPublic HTTPS POST /qefro
Signing secretShared HMAC (QEFRO_SIGNING_SECRET)
EnabledKill switch

Setup workflow

  1. SDK Connections → webhook URL + signing secret.
  2. Test Connection — signed pingpong + sdk_version.
  3. Select workspace → Sync Tools (tools.list + optional auto-register).
POST/api/v1/org/sdk-connections/:id/test

Signed ping; updates connection health.

POST/api/v1/org/sdk-connections/:id/sync-tools

tools.list + optional auto_register into workspace.

{
"workspace_id": "uuid",
"auto_register": true,
"enable_new_tools": true
}
Sync writesSource
implementation_kindsdk
sdk_handler_nameTool name from tools.list
description, input_schemaFrom handler metadata
preconditions.lookup_requiredTool lookup.required
required_auth_levelorganization_challenge when auth methods set

After you add or rename handlers, deploy your backend and run Sync Tools again. Guide: Register SDK Business Tools.

Return values

Return structured JSON. Include clear message fields for the LLM. Throw or return error shapes the runtime maps to user-safe text.

Examples repository

ExamplePath
Order status + OTPorder-status
REST identity mockrest-order-api
Banking, healthcare, CRMexamples/

Catalog: Examples.

FAQ

Do I implement /ping myself?
No. app.listen() handles ping, tools.list, tool.invoke, and tool.resume.
Why does synced SDK tool show a placeholder URL?
SDK tools execute via webhook + sdk_handler_name, not the URL field. The placeholder is internal.