Create a webhook subscription
lob_webhooks_createSet up a webhook to monitor mail delivery events by registering an HTTPS URL and selecting event types.
Instructions
Subscribe an HTTPS endpoint to receive Lob event notifications (e.g. 'postcard.mailed', 'letter.in_transit', 'check.delivered'). The endpoint must respond with 2xx within 5 seconds.
Input Schema
| Name | Required | Description | Default |
|---|---|---|---|
| url | Yes | HTTPS URL to receive event POSTs. | |
| event_types | Yes | Event types to subscribe to, e.g. ['postcard.mailed', 'letter.delivered']. Use ['*'] for all. | |
| description | No | ||
| metadata | No | Up to 20 string key/value pairs of arbitrary metadata to attach to the resource. | |
| extra | No | Additional Lob API parameters not enumerated above. Merged into the request body verbatim. See https://docs.lob.com for the full parameter list per resource. |
Implementation Reference
- src/tools/webhooks.ts:20-45 (handler)The tool 'lob_webhooks_create' registration and handler. It registers with input schema (url, event_types, description, metadata, extra) and the handler POSTs to /webhooks with the given payload via lob.request.
export function registerWebhookTools(server: McpServer, lob: LobClient): void { registerTool(server, { name: "lob_webhooks_create", annotations: { title: "Create a webhook subscription", ...ToolAnnotationPresets.mutate }, description: "Subscribe an HTTPS endpoint to receive Lob event notifications (e.g. 'postcard.mailed', " + "'letter.in_transit', 'check.delivered'). The endpoint must respond with 2xx within 5 seconds.", inputSchema: { url: z.string().url().describe("HTTPS URL to receive event POSTs."), event_types: z .array(z.string()) .min(1) .describe("Event types to subscribe to, e.g. ['postcard.mailed', 'letter.delivered']. Use ['*'] for all."), description: z.string().max(255).optional(), metadata: metadataSchema, extra: extraParamsSchema, }, handler: async (args) => { const { extra, ...rest } = args; return lob.request({ method: "POST", path: "/webhooks", body: withExtra(rest, extra), }); }, }); - src/tools/webhooks.ts:27-36 (schema)Input schema for lob_webhooks_create: url (string URL), event_types (array of strings, min 1), description (optional string max 255), metadata (optional key/value record), extra (optional record for additional Lob API parameters).
inputSchema: { url: z.string().url().describe("HTTPS URL to receive event POSTs."), event_types: z .array(z.string()) .min(1) .describe("Event types to subscribe to, e.g. ['postcard.mailed', 'letter.delivered']. Use ['*'] for all."), description: z.string().max(255).optional(), metadata: metadataSchema, extra: extraParamsSchema, }, - src/tools/register.ts:44-44 (registration)Registration call: registerWebhookTools(server, lob) at line 44 of register.ts wires the webhooks group into the MCP server.
registerWebhookTools(server, lob); - src/tools/helpers.ts:85-117 (helper)The registerTool helper that wraps the handler with error handling and registers the tool on the MCP server.
export function registerTool<TShape extends ZodRawShape>( server: McpServer, def: ToolDefinition<TShape>, ): void { const a = def.annotations ?? {}; server.registerTool( def.name, { title: a.title ?? def.name, description: def.description, inputSchema: def.inputSchema, annotations: { ...a, // Lob is always external; default the hint accordingly. openWorldHint: a.openWorldHint ?? true, }, }, // The SDK's ToolCallback type is parameterised over the exact ZodRawShape and // resists the generic erasure here. The runtime contract (validated args in, // CallToolResult out) is correct, so we bridge the type boundary with `as never`. (async (args: unknown, serverCtx: unknown): Promise<CallToolResult> => { try { const result = await def.handler(args as never, serverCtx); return { content: [{ type: "text", text: stringifyResult(result) }] }; } catch (err) { return { isError: true, content: [{ type: "text", text: formatErrorForTool(err) }], }; } }) as never, ); } - src/schemas/common.ts:160-166 (helper)The withExtra helper used by the handler to merge extra parameters into the request body.
/** Merge an `extra` record into a typed payload, with explicit fields taking precedence. */ export function withExtra( payload: object, extra: Record<string, unknown> | undefined, ): Record<string, unknown> { return { ...(extra ?? {}), ...compact(payload) }; }