Next liveBuild Your Chief of Staff · OnlineSave your seat →
Custom Actions

Integration Examples

Common custom action patterns and real-world integration examples

|View as Markdown
1 min read
ℹ️

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

bash
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

ParameterSourceConfiguration
messageAI fills it inMessage content to send. Type Text, Required
channelAI fills it inTarget channel. Type Text, Required off
SLACK_WEBHOOK_IDApplication variableYour 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

bash
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

ParameterSourceConfiguration
OPENAI_API_KEYApplication variable (secret)Your OpenAI API key, used in the header
inputAI fills it inText for OpenAI to process. Type Text, Required
max_output_tokensAI fills it inToken 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

bash
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

bash
SENDGRID_API_KEY = SG.abc123...
FROM_EMAIL = noreply@example.com
FROM_NAME = Your Company

Google Sheets

Read and write data to Google Sheets.

Read Data

bash
curl -X GET "https://sheets.googleapis.com/v4/spreadsheets/{{var.SHEET_ID}}/values/{{range}}" \
  -H "Authorization: Bearer {{var.GOOGLE_API_KEY}}"

Append Data

bash
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

bash
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:

bash
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

bash
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:

plaintext
Summarize the key points from the conversation in 1-2 sentences

For status parameter:

plaintext
Determine status: 'interested', 'qualified', 'not-interested', or 'follow-up'

Stripe Payment

Create payment intents via Stripe API.

Configuration

bash
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}}&currency=usd&customer={{customerId}}"

Parameters

ParameterSourceConfiguration
STRIPE_SECRET_KEYApplication variable (secret)Your Stripe secret key
amountAI fills it inAmount in cents. Type Whole number
currencyFixed valueusd
customerAI fills it inStripe 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

bash
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

bash
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

ParameterSourceTypeAI Instructions
titleAI fills it inTextCreate a concise title for the GitHub issue
bodyAI fills it inTextWrite a detailed description of the issue
labelsAI fills it inListRelevant labels, for example [“bug”, “urgent”]. Required off
assigneesAI fills it inListGitHub 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.

FieldRequiredTypeNotes
messageIdYesTextThe Meta message ID (wamid), for example wamid.HBgL.... Must start with wamid.. 7 to 256 characters.
recipientPhoneYesText or whole numberThe 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.
templateNameYesTextThe Meta template name. Up to 512 characters.
languageCodeNoTextFor 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.
renderedBodyNoTextThe message text the customer saw, with the variables filled in. Up to 4096 characters.
componentsNoListThe 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.
buttonsNoListUp to 10 entries of title (required, up to 200 characters) and payload (optional, up to 512 characters).
correlationNoObjectShort 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

json
{
  "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.

javascript
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

  1. First, the top-level whatsappTemplateSend key of the response.
  2. 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 example data.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 messageId as 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:

json
{
  "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

Next Steps