Integration Examples
Common custom action patterns and real-world integration examples
Custom actions require a Builder plan or higher.
Upgrade to Builder →
Body parameters are sent as top-level JSON keys. When you import a cURL whose body has nested objects or arrays, the importer creates dotted keys such as from.email, and those are sent as flat keys, not nested JSON. For an API that needs a nested body, replace those rows with one AI fills it in parameter of type Object or List (see Nested Body Fields).
Slack Notifications
Send messages to Slack channels via webhook.
Configuration
curl -X POST https://hooks.slack.com/services/{{var.SLACK_WEBHOOK_ID}} \
-H "Content-Type: application/json" \
-d '{
"text": "{{message}}",
"channel": "{{channel}}",
"username": "Chipp Bot"
}'Parameters
| Parameter | Source | Configuration |
|---|---|---|
message | AI fills it in | Message content to send. Type Text, Required |
channel | AI fills it in | Target channel. Type Text, Required off |
SLACK_WEBHOOK_ID | Application variable | Your Slack webhook ID, used in the URL |
Usage
“Send a message to #general saying the report is ready”
OpenAI Completions
Call OpenAI’s API for advanced processing.
Configuration
curl -X POST https://api.openai.com/v1/responses \
-H "Authorization: Bearer {{var.OPENAI_API_KEY}}" \
-H "Content-Type: application/json" \
-d '{
"model": "gpt-4.1",
"input": "{{prompt}}",
"max_output_tokens": {{maxTokens}}
}'Parameters
| Parameter | Source | Configuration |
|---|---|---|
OPENAI_API_KEY | Application variable (secret) | Your OpenAI API key, used in the header |
input | AI fills it in | Text for OpenAI to process. Type Text, Required |
max_output_tokens | AI fills it in | Token limit. Type Whole number, Required off, sample 1000 |
Set Type to Whole number on max_output_tokens, so the AI sends a number and not the text "1000".
SendGrid Email
Send transactional emails via SendGrid.
Configuration
curl -X POST https://api.sendgrid.com/v3/mail/send \
-H "Authorization: Bearer {{var.SENDGRID_API_KEY}}" \
-H "Content-Type: application/json" \
-d '{
"personalizations": [{
"to": [{"email": "{{recipientEmail}}"}],
"subject": "{{subject}}"
}],
"from": {
"email": "{{var.FROM_EMAIL}}",
"name": "{{var.FROM_NAME}}"
},
"content": [{
"type": "text/html",
"value": "{{emailBody}}"
}]
}'Variables
SENDGRID_API_KEY = SG.abc123...
FROM_EMAIL = noreply@example.com
FROM_NAME = Your CompanyGoogle Sheets
Read and write data to Google Sheets.
Read Data
curl -X GET "https://sheets.googleapis.com/v4/spreadsheets/{{var.SHEET_ID}}/values/{{range}}" \
-H "Authorization: Bearer {{var.GOOGLE_API_KEY}}"Append Data
curl -X POST "https://sheets.googleapis.com/v4/spreadsheets/{{var.SHEET_ID}}/values/{{range}}:append" \
-H "Authorization: Bearer {{var.GOOGLE_API_KEY}}" \
-H "Content-Type: application/json" \
-d '{
"values": [[
"{{system.timestamp}}",
"{{system.user_id}}",
"{{data}}"
]]
}'Webhook with Dependencies
Multi-step workflow using action dependencies.
Step 1: Create User
curl -X POST https://api.example.com/users \
-H "Content-Type: application/json" \
-H "Authorization: Bearer {{var.API_KEY}}" \
-d '{
"name": "{{userName}}",
"email": "{{userEmail}}"
}'Returns: {"data": {"id": "user_123", "email": "john@example.com"}}
Step 2: Send Welcome Email
Use the user ID from Step 1:
curl -X POST https://api.example.com/emails \
-H "Content-Type: application/json" \
-H "Authorization: Bearer {{var.API_KEY}}" \
-d '{
"user_id": "{{userId}}",
"template": "welcome",
"send_at": "{{system.timestamp}}"
}'Parameter configuration for user_id:
- Source: Output from another action
- Action: Create User
- JSONPath:
$.data.id - Test value:
user_123(sent by Test Request, which has no earlier action output)
Save “Create User” first: the action list only shows saved actions. If the AI calls “Send Welcome Email” before “Create User” has run in the conversation, the call stops and the AI is told to run “Create User” first.
CRM Integration
Update customer records in your CRM.
Configuration
curl -X PATCH https://api.crm.com/v2/contacts/{{contactId}} \
-H "Authorization: Bearer {{var.CRM_API_KEY}}" \
-H "Content-Type: application/json" \
-d '{
"properties": {
"last_contact": "{{system.timestamp}}",
"last_message": "{{summary}}",
"status": "{{status}}"
}
}'AI Instructions
For summary parameter:
Summarize the key points from the conversation in 1-2 sentencesFor status parameter:
Determine status: 'interested', 'qualified', 'not-interested', or 'follow-up'Stripe Payment
Create payment intents via Stripe API.
Configuration
curl -X POST https://api.stripe.com/v1/payment_intents \
-u "{{var.STRIPE_SECRET_KEY}}:" \
-H "Content-Type: application/x-www-form-urlencoded" \
-d "amount={{amount}}¤cy=usd&customer={{customerId}}"Parameters
| Parameter | Source | Configuration |
|---|---|---|
STRIPE_SECRET_KEY | Application variable (secret) | Your Stripe secret key |
amount | AI fills it in | Amount in cents. Type Whole number |
currency | Fixed value | usd |
customer | AI fills it in | Stripe customer ID. Type Text |
The importer reads a form-encoded -d string as a single data parameter, so add these three body parameters by hand. It also turns -u into a fixed Authorization header that encodes the placeholder text itself, which Chipp cannot resolve. Delete that header and set the Auth tab to Basic Auth with the STRIPE_SECRET_KEY variable as the credential (Stripe uses the key as the username and an empty password). With the Content-Type: application/x-www-form-urlencoded header set, Chipp sends them form-encoded.
Weather API
Fetch weather data for user queries.
Configuration
curl -X GET "https://api.openweathermap.org/data/2.5/weather" \
-G \
--data-urlencode "q={{city}}" \
--data-urlencode "appid={{var.WEATHER_API_KEY}}" \
--data-urlencode "units=metric"Usage Examples
“What’s the weather in New York?” “Is it raining in London?”
GitHub Integration
Create issues in GitHub repositories.
Configuration
curl -X POST https://api.github.com/repos/{{var.GITHUB_OWNER}}/{{var.GITHUB_REPO}}/issues \
-H "Authorization: Bearer {{var.GITHUB_TOKEN}}" \
-H "Content-Type: application/json" \
-d '{
"title": "{{issueTitle}}",
"body": "{{issueBody}}",
"labels": {{labels}},
"assignees": {{assignees}}
}'Parameter Configuration
| Parameter | Source | Type | AI Instructions |
|---|---|---|---|
title | AI fills it in | Text | Create a concise title for the GitHub issue |
body | AI fills it in | Text | Write a detailed description of the issue |
labels | AI fills it in | List | Relevant labels, for example [“bug”, “urgent”]. Required off |
assignees | AI fills it in | List | GitHub usernames to assign. Required off |
Log WhatsApp templates sent through a proxy
If your action calls the Meta WhatsApp Cloud API directly (graph.facebook.com/vXX/PHONE_ID/messages), Chipp already records the template in the conversation. You do not need this section.
If a proxy sends the template for you (Apps Script, n8n, Make, Zapier, or your own server), Chipp cannot see the send. The customer gets the template, but the conversation has no record of it. When the customer then taps a button, the AI has no context for the tap.
To fix this, make the proxy return a whatsappTemplateSend object in its JSON response. When Chipp finds a valid one, it saves the template as an assistant message in that customer’s WhatsApp conversation. The AI reads it as earlier context, and it shows in Conversations before the customer’s reply.
Without the object, nothing changes: the action works as before and Chipp saves nothing.
The whatsappTemplateSend fields
Put the object at the top level of the JSON response. Chipp ignores every other key in the object.
| Field | Required | Type | Notes |
|---|---|---|---|
messageId | Yes | Text | The Meta message ID (wamid), for example wamid.HBgL.... Must start with wamid.. 7 to 256 characters. |
recipientPhone | Yes | Text or whole number | The customer’s phone number. Any format works (+1 555 123 4567). Chipp keeps the digits. Must have 7 to 15 digits. You can send waId or wa_id instead of recipientPhone. |
templateName | Yes | Text | The Meta template name. Up to 512 characters. |
languageCode | No | Text | For example en or en_US. Up to 35 characters. If you leave it out, Chipp uses the language of the one template in your app with that name. If there is none, or more than one, it uses und. |
renderedBody | No | Text | The message text the customer saw, with the variables filled in. Up to 4096 characters. |
components | No | List | The components array you sent to Meta. Chipp uses it to rebuild the text and the button payloads when you do not send renderedBody. Up to 20 entries. |
buttons | No | List | Up to 10 entries of title (required, up to 200 characters) and payload (optional, up to 512 characters). |
correlation | No | Object | Short references such as an order ID. Text, number and true/false values only. Up to 10 entries, and each text value is cut at 200 characters. Keys that look like secrets (token, secret, password, auth, api_key, and similar) are dropped. |
If a field is present but breaks a rule above, for example messageId does not start with wamid., Chipp ignores the whole object. A field set to null counts as left out.
Send either renderedBody or components so the saved message shows the real text. With neither, Chipp looks the template text up in your Meta account. If that fails, it saves only the template name and the variable values.
Example response
{
"success": true,
"whatsappTemplateSend": {
"messageId": "wamid.HBgLMTU1NTEyMzQ1NjcVAgARGBI5QUFBQUFBQUFBQUFBQUFBAA==",
"recipientPhone": "15551234567",
"templateName": "order_update",
"languageCode": "en",
"renderedBody": "Hi Sam, your order is ready to ship on Tuesday. Please confirm.",
"buttons": [
{ "title": "Yes, confirm", "payload": "CONFIRM_ORDER:ORD-1001" },
{ "title": "No, change it", "payload": "CHANGE_ORDER:ORD-1001" }
],
"correlation": { "order_id": "ORD-1001" }
}
}Apps Script example
This script sends the template, then copies the message ID and phone number from Meta’s reply. Keep the access token in Project Settings > Script properties, never in the code and never in the response.
function doPost(e) {
var props = PropertiesService.getScriptProperties();
var body = JSON.parse(e.postData.contents);
var res = UrlFetchApp.fetch(
"https://graph.facebook.com/v22.0/" + props.getProperty("PHONE_NUMBER_ID") + "/messages",
{
method: "post",
contentType: "application/json",
headers: { Authorization: "Bearer " + props.getProperty("WHATSAPP_TOKEN") },
payload: JSON.stringify({
messaging_product: "whatsapp",
to: body.to,
type: "template",
template: {
name: "order_update",
language: { code: "en" },
components: body.components
}
}),
muteHttpExceptions: true
}
);
var meta = JSON.parse(res.getContentText());
if (!meta.messages || !meta.messages[0]) {
// Meta refused the send. Return no envelope, so nothing is saved.
return ContentService.createTextOutput(JSON.stringify({ success: false }))
.setMimeType(ContentService.MimeType.JSON);
}
return ContentService.createTextOutput(JSON.stringify({
success: true,
whatsappTemplateSend: {
messageId: meta.messages[0].id,
waId: meta.contacts && meta.contacts[0] ? meta.contacts[0].wa_id : body.to,
templateName: "order_update",
languageCode: "en",
components: body.components,
correlation: { order_id: body.order_id }
}
})).setMimeType(ContentService.MimeType.JSON);
}Deploy the web app with access set to Anyone. Apps Script answers a POST with a redirect, and Chipp follows it, so the envelope in the final JSON is still found.
Where Chipp looks
- First, the top-level
whatsappTemplateSendkey of the response. - If that key is missing, a response mapping field (set in the action’s Response mapping settings) whose key is
whatsappTemplateSend. Its source can point to a nested object, for exampledata.send. This helps when your proxy wraps the reply and you cannot change it.
Chipp reads the original response from the proxy, before the response mapping changes it. The AI still sees only the mapped result.
Notes
- No duplicates. Chipp uses
messageIdas the key. If the action runs twice and returns the same ID, there is still one message. A new send has a new wamid, so it adds a new message. - Safe on errors. If the object is invalid or saving fails, Chipp logs it and the action still succeeds. The AI gets the same result as before. The log names the bad fields but never their values.
- Same conversation. The message goes to the WhatsApp conversation for this app and this phone number, the same one the customer’s later reply uses. The app is always the one that owns the action.
- No secrets in the response. Chipp saves only the fields in the table above, and nothing from your request or headers. Even so, never return access tokens, API keys, or passwords in the response. The AI can see the response.
- Zero data retention. Apps with zero data retention (ZDR) save no message text, so nothing is recorded for them.
Best Practices
1. Error Handling
Always return clear error messages:
{
"success": false,
"error": "API key invalid. Please check your credentials."
}2. Rate Limiting
Respect API rate limits:
- Add delays between requests
- Implement exponential backoff
- Cache responses when possible
3. Security
- Use HTTPS endpoints only
- Store credentials as secret variables
- Never log sensitive data
- Validate all inputs
4. Performance
- Paginate large result sets
- Use field selection to minimize data
- Implement timeouts (30s max)
- Cache frequently accessed data
5. Testing
- Test with various inputs
- Verify error handling
- Check edge cases
- Monitor response times