Provider-aware function helpers
Build Slack, Discord, Telegram, Notion, Airtable, Google Sheets and Supabase requests, or enrich webhooks with managed or customer-provided AI.
Provider-aware helpers remove repetitive payload mapping from Functions. With one exception, they are pure builders: they return a body or request descriptor and never contact the destination. Apply that result to an output so retries, delivery logs, throttling and response functions continue to work.
ai.generate() is the exception. It performs a bounded synchronous enrichment
request through a typed AI provider connection.
Webhook Relay AI
Create a Webhook Relay AI connection and bind it to ai:writer. It needs no
API key, selects the model for you, and shares the account agent's monthly AI
token allowance:
const ai = require("ai:writer")
const result = ai.generate("Summarize: " + r.body, {
maxTokens: 200
})
r.setBody(JSON.stringify({ summary: result.text, usage: result.usage }))
Do not pass model or temperature for Webhook Relay AI. The platform manages
those settings.
OpenAI
Create an OpenAI service connection and bind it to ai:writer:
const ai = require("ai:writer")
const result = ai.generate("Summarize: " + r.body, {
model: "gpt-5-mini",
maxTokens: 200
})
r.setBody(JSON.stringify({ summary: result.text, usage: result.usage }))
Google Gemini
The same helper uses Gemini's generateContent API when the alias is bound to
a Gemini connection:
const ai = require("ai:gemini")
const result = ai.generate("Extract the customer intent from: " + r.body, {
model: "gemini-2.5-flash",
maxTokens: 100
})
r.setHeader("X-Customer-Intent", result.text.trim())
Anthropic
Bind the alias to an Anthropic connection and supply an Anthropic model name:
const ai = require("ai:reviewer")
const result = ai.generate("Review this event for anomalies: " + r.body, {
model: "claude-haiku-4-5",
system: "Be concise.",
maxTokens: 200
})
r.setBody(JSON.stringify({ review: result.text }))
Custom Chat Completions API
A custom connection requires an HTTPS base URL whose API implements OpenAI's
POST /chat/completions request and response shape:
const ai = require("ai:private-model")
const result = ai.generate("Label this event: " + r.body, {
model: "company-classifier-v2",
maxTokens: 32,
timeoutMs: 5000
})
r.setHeader("X-Label", result.text.trim())
API keys for all four customer-provided AI backends stay in the host runtime. Function code can
call generate but cannot read the key. See AI provider connections
for setup, structured JSON output and limits.
If the alias is missing, the account agent links you to the Function's Connections tab. Webhook Relay AI can be created there without credentials; enter customer-provider credentials only in the write-only Service Connections dialog, never in agent chat.
Slack
Use slack.message() for text or slack.blocks() for Block Kit. Configure a
standard public output with the Slack incoming webhook URL as its destination;
the helper only builds its JSON body. This is distinct from the preformatted
Slack notification output, which wraps arbitrary webhook bodies for you.
const slack = require("slack")
const event = JSON.parse(r.body)
const body = slack.blocks([
{ type: "section", text: { type: "mrkdwn", text: "*Deploy complete*" } },
{ type: "context", elements: [{ type: "mrkdwn", text: event.version }] }
], { text: "Deploy complete: " + event.version })
r.setBody(JSON.stringify(body))
r.setHeader("Content-Type", "application/json")
Slack message payloads accept at most 50 blocks. options.text is required as
the notification and accessibility fallback. When delivering to Slack's
chat.postMessage API instead of an incoming webhook, pass the required
channel as an option, for example slack.message("Build passed", {channel: "C123"}).
Discord
discord.embed() validates Discord's title, description, field and aggregate
text limits. Wrap the returned embed in the webhook API's embeds array and
use a standard public output targeting the Discord webhook URL:
const discord = require("discord")
const embed = discord.embed("Deploy complete", {
description: "Production is healthy",
color: 3066993,
fields: [{ name: "Version", value: "2026.09.09", inline: true }]
})
r.setBody(JSON.stringify({ embeds: [embed] }))
r.setHeader("Content-Type", "application/json")
Telegram
The Telegram helpers return method, path and body. Apply all three to a
standard HTTP output whose destination is
https://api.telegram.org/bot<TOKEN>:
const telegram = require("telegram")
const request = telegram.message("-1001234567890", "Build passed", {
disable_notification: true
})
r.setMethod(request.method)
r.setPath(request.path)
r.setBody(JSON.stringify(request.body))
r.setHeader("Content-Type", "application/json")
For an inline-button webhook, use
telegram.answerCallback(event.callback_query.id, {text: "Done"}) and apply
the returned descriptor in the same way. The helper does not include a bot
token. Telegram authentication is part of the Bot API URL, and there is not
currently a dedicated Telegram output connection, so the configured output
destination contains the token.
Notion
notion.properties() maps ordinary values into typed Notion page properties.
Use the result as properties in a page create or update output:
const notion = require("notion")
const event = JSON.parse(r.body)
const properties = notion.properties({
Name: { type: "title", value: event.customer.name },
Status: { type: "status", value: "New" },
Amount: { type: "number", value: event.amount },
Received: { type: "date", value: event.created_at }
})
r.setBody(JSON.stringify({
parent: { type: "data_source_id", data_source_id: "DATA_SOURCE_ID" },
properties
}))
r.setHeader("Content-Type", "application/json")
r.setHeader("Notion-Version", "2026-03-11")
Store the Notion integration token as a secret connection and set its Bearer header in the Function or output configuration.
Airtable
airtable.fields() wraps a record for Airtable and can enable API typecasting:
const airtable = require("airtable")
const event = JSON.parse(r.body)
const body = airtable.fields({
Name: event.customer.name,
Email: event.customer.email,
Active: true
}, { typecast: true })
r.setBody(JSON.stringify(body))
r.setHeader("Content-Type", "application/json")
This example creates one record. Use an Airtable personal access token from a
secret connection for the Authorization: Bearer … header. The output
destination remains the Airtable records endpoint.
Google Sheets
sheets.row() creates a ValueRange with stable column order for the Sheets
spreadsheets.values.append endpoint:
const sheets = require("sheets")
const event = JSON.parse(r.body)
const row = sheets.row(event, ["id", "email", "amount", "created_at"])
r.setBody(JSON.stringify(row))
r.setHeader("Content-Type", "application/json")
Missing object properties become null cells. Authentication and the sheet
range belong to the configured output; the helper does not append the row.
Supabase
supabase.insert() and supabase.upsert() return a PostgREST request
descriptor. Apply its method, path, optional raw query, headers and body to an
output targeting the Supabase project URL:
const supabase = require("supabase")
const request = supabase.upsert("events", JSON.parse(r.body), {
onConflict: "id",
returning: "representation"
})
r.setMethod(request.method)
r.setPath(request.path)
if (request.rawQuery) r.setRawQuery(request.rawQuery)
Object.keys(request.headers).forEach(name => r.setHeader(name, request.headers[name]))
r.setBody(JSON.stringify(request.body))
Import the Supabase key from a secret connection and set the apikey and
Authorization headers, or configure those headers on the output. Keeping the
write as an output makes an upsert observable and replayable through the normal
delivery system.
Lua usage
Every builder uses the same module and camelCase method names in Lua. Tables replace JavaScript objects and request setters use colon syntax:
local slack = require("slack")
local json = require("json")
local body = slack.message("Build passed")
local encoded, err = json.encode(body)
if err then error(err) end
r:SetRequestBody(encoded)
r:SetRequestHeader("Content-Type", "application/json")
The generated Function reference in the editor includes runnable JavaScript
and Lua examples for every method, including telegram.answerCallback() and
both Supabase write builders.
