# Ampersand AI SDK Source: https://docs.ampersand.ai/ai-sdk Install the Ampersand AI SDK and use tools like createRecord and updateRecord in agents built with the Vercel AI SDK or Mastra to act on SaaS platforms. The [Ampersand AI SDK](https://www.npmjs.com/package/@amp-labs/ai) enables AI agents to seamlessly perform operations on connected SaaS tools through Ampersand's platform. The Ampersand AI SDK is in alpha. It may change in non backwards-compatible ways. (Although we are very serious about semantic versioning.) If you have any feedback, please file an issue [on Github](https://github.com/amp-labs/ai/issues). ## Installation ```bash theme={"dark"} npm install @amp-labs/ai # or yarn add @amp-labs/ai # or pnpm add @amp-labs/ai ``` ## Usage The SDK provides several modules that can be used depending on your framework preference: ### Use with Vercel AI SDK [AI SDK](https://ai-sdk.dev/) by Vercel unifies LLM access in TypeScript & makes it easy to build AI agents. This is a simple example of how to use the AI SDK tools by Ampersand in a Vercel AI agent. ```typescript theme={"dark"} import { openai } from '@ai-sdk/openai'; import { generateObject, generateText } from 'ai'; import { z } from 'zod'; import { createRecord, updateRecord } from "@amp-labs/ai/aisdk"; // Vercel Agent with AI SDK tools by Ampersand async function handleCustomerQuery(query: string) { const model = openai('gpt-4o'); const { text: response } = await generateText({ model: classification.complexity === 'simple' ? openai('gpt-4o-mini') : openai('o3-mini'), system: '... your system prompt here', prompt: query, tools: { // AI SDK tools by Ampersand being used here createRecord, updateRecord, }, }); return { response }; } ``` ### Use with Mastra [Mastra](https://mastra.ai) is a Typescript toolkit that offers agent workflows and eval tools so teams can turn prototypes into production-ready AI agents. This is a simple example of how to use the AI SDK tools by Ampersand in an agent built with Mastra. ```typescript theme={"dark"} import { Agent } from "@mastra/core/agent"; import { openai } from "@ai-sdk/openai"; import { createRecord, updateRecord } from "@amp-labs/ai/mastra"; // Mastra Agent with Mastra tools export const mastraToolsAgent: Agent = new Agent({ name: "Mastra Tools Agent", instructions: "You can use tools defined in Mastra.", model: openai("gpt-4o-mini"), tools: { createRecord, updateRecord, }, }); ``` ## Available Tools The SDK provides the following tools for interacting with SaaS platforms: | Tool | Description | Purpose | | - | - | - | | `createRecord` | Creates a new record in the connected SaaS platform | Create new records like contacts, deals, tickets etc. | | `updateRecord` | Updates an existing record in the connected SaaS platform | Modify existing records with new data | | `sendReadRequest` | Makes authenticated GET API calls to the provider through Ampersand | Reading and searching records | | `sendRequest` | Makes authenticated API calls to the provider through Ampersand | Do any action that is available via the API | | `checkConnection` | Verifies if there is an active connection to the SaaS provider | Validate connection status before creating an installation | | `createInstallation` | Creates a new installation for a provider | Create an installation for an existing connection | | `checkInstallation` | Checks if there is an active installation for a provider | Verify installation status | | `startOAuth` | Returns URL for initiating OAuth flow for connecting to a provider | Authenticates a new user | Have a tool in mind that we could add? Let us know [on Discord!](https://discord.gg/BWP4BpKHvf) # Access via JWT Source: https://docs.ampersand.ai/api/jwt-auth Generate an RSA key pair, register the public key with Ampersand, sign JWTs on your server, and fetch them in your frontend to authenticate API requests. This guide explains how to generate and use JWTs for authentication, so that you can securely interact with Ampersand APIs from your frontend and your customers will only have access to their own Ampersand installations. When you first start using Ampersand's [prebuilt UI components](/embeddable-ui-components) or [headless UI](/headless), using a frontend API key is convenient since it does not require you to implement any server logic. However, as you move into production, we recommend that you stop using frontend API keys and start using JWT authentication for better security. Do not do JWT signing in the frontend. It invalidates the security model. ## Overview The overall flow uses RSA-based JWT tokens with RS256 signing. It consists of: 1. **RSA Key Pair Generation** - Create a public/private key pair. 2. **Key Registration** - Register the public key with Ampersand. 3. **JWT Token Generation** - Create signed JWT tokens in your server. 4. **API Authentication** - Use JWTs to authenticate API requests made by your frontend code, by providing a `getToken` method to the Ampersand UI library. ## 1. Generate RSA key pair First, generate an RSA key pair for signing JWTs. You can use any crypto library that can generate an RSA key pair. Only RS256 algorithm is supported. The example below uses the Node.js library `crypto`. You only need to do this step when you first implement JWT auth, and every time you want to rotate keys. ```javascript theme={"dark"} const crypto = require('crypto'); const fs = require('fs'); // Generate RSA key pair (2048-bit recommended) const { publicKey, privateKey } = crypto.generateKeyPairSync('rsa', { modulusLength: 2048, publicKeyEncoding: { type: 'spki', format: 'pem' }, privateKeyEncoding: { type: 'pkcs1', format: 'pem' } }); // Persist the keys somewhere fs.writeFileSync('private_key.pem', privateKey); fs.writeFileSync('public_key.pem', publicKey); console.log('RSA key pair generated successfully!'); ``` ## 2. Register public key with Ampersand ### Using the Ampersand Dashboard You can add your RSA public key and view your existing Key IDs (kid) directly in the [Ampersand Dashboard](https://dashboard.withampersand.com/projects/_/jwt-keys). JWT Key Management in Dashboard ### Using the API You can also register the public key programmatically using the [Create a new JWT key endpoint](/reference/jwt-key/create-a-new-jwt-key). ## 3. Generate JWT tokens in server Create JWT tokens for a particular `groupRef` and `consumerRef` combo using public and private keys generated in step 1. You can use any library of choice that can sign JWT keys. The example below uses the Node.js library `jsonwebtoken`. You need to then expose an API endpoint that your frontend code can call to fetch a JWT token. ```javascript theme={"dark"} const jwt = require('jsonwebtoken'); const fs = require('fs'); // Function for generating JWT function generateJWT(keyId, claims, ttlSeconds = 600) { const privateKey = fs.readFileSync('private_key.pem', 'utf8'); // JWT payload with required claims const payload = { sub: claims.subject, groupRef: claims.groupRef, consumerRef: claims.consumerRef, iat: Math.floor(Date.now() / 1000), // Issued at time // Expiry time, the max TTL allowed is 600 seconds. exp: Math.floor(Date.now() / 1000) + ttlSeconds }; // Sign the token const token = jwt.sign(payload, privateKey, { algorithm: 'RS256', header: { kid: keyId // Key ID returned by Ampersand in step 2. } }); return token; } // Expose an API that your frontend code can call. // This example assumes you are using Express. // You need to adapt this code to your server framework. const app = express(); app.use(express.json()); app.post('/api/generate-jwt', async (req, res) => { try { const { groupRef, consumerRef } = req.body; const token = generateJWT('your-key-id', { groupRef: groupRef, consumerRef: consumerRef, // The subject can be any string that helps you with debugging, // such as username, email, etc. Ampersand doesn't pay attention // to this field. subject: 'user@example.com', }, 300); // 5 minute expiration res.json(token); } catch (error) { res.status(500).json({ error: 'Internal server error', message: 'Failed to generate JWT token' }); } }); ``` ## 4. Fetch JWT token in frontend This functionality is only available in `@amp-labs/react` version `2.8.7` and above. In [AmpersandProvider](/embeddable-ui-components#ampersandprovider), instead of using the `apiKey` property, provide a `getToken` method that calls your backend to fetch a JWT token. The UI library will call it whenever it needs to generate a new token, or when the existing token expires. The `getToken` method must have the following type signature: `(params: {consumerRef: string, groupRef: string}) => Promise` ```javascript theme={"dark"} import { AmpersandProvider } from '@amp-labs/react'; import '@amp-labs/react/styles'; const options = { project: 'my-ampersand-project', getToken: ({consumerRef, groupRef}) => { const token = await getTokenFromMyBackend(consumerRef, groupRef); return token; } }; function App() { return ( // You can use any of the Ampersand components here. ) } ``` ## Key management API endpoints The backend provides these endpoints for managing JWT keys: * [Register a new public key](/reference/jwt-key/create-a-new-jwt-key) * [List all keys for a project](/reference/jwt-key/list-jwt-keys) * [Get a specific key](/reference/jwt-key/get-a-specific-jwt-key) * [Update a key's label or status](/reference/jwt-key/update-a-jwt-key) * [Delete a key](/reference/jwt-key/delete-a-jwt-key) ## Important security notes 1. **Private Key Security**: Never expose private keys in client-side code. Store them securely on your backend servers. 2. **Token Expiration**: Tokens can have a maximum TTL of 10 minutes (600 seconds). The Ampersand UI library will automatically call the `getToken` function you provide when the token expires. 3. **Key Rotation**: Regularly rotate your RSA key pairs for better security. # Access via API key Source: https://docs.ampersand.ai/api/key-auth Authenticate Ampersand API requests with the X-Api-Key header, and create, name, and view API keys from the API Keys page in the Ampersand Dashboard. ## Using the API All API endpoints require authenticated access via an API key passed as a header `X-Api-Key`: your Ampersand API key. If you don't have one yet, create as many as you'd like on the Ampersand Dashboard's [API Keys page](https://dashboard.withampersand.com/projects/_/api-keys). ## Creating and Viewing API Key Generating an API key 1. Go to the [API Keys page](https://dashboard.withampersand.com/projects/_/api-keys) on the Ampersand Dashboard. 2. Click on the "New API Key" button. 3. Give your API key a descriptive name and click on the "Save Changes" button. 4. Your API key will be generated. You can view it by clicking on the button with the name of the key. Copy API key # Ampersand REST APIs: Write, Read, Platform, and Proxy Source: https://docs.ampersand.ai/api/overview Learn when to use Ampersand's four REST APIs (Write, Read, Platform, and Proxy) instead of your amp.yaml manifest, and try them in the Postman Collection. Reference documentation for Ampersand's REST APIs. Most integration behavior is declared in your `amp.yaml` manifest rather than called directly. If you're setting up your first integration, start with [how Ampersand works](/concepts) and the [starter project](/starter-project). Come back here when you need to trigger a read on demand, write records from your backend, manage installations programmatically, or call a provider endpoint Ampersand doesn't model. Ampersand offers 4 APIs: * **Write API**: for writing data to your customer's SaaS instance. Learn more in [Write Actions](/write-actions). * **Read API**: for advanced read use cases like triggering an off-schedule read of your customer's SaaS instance, or for managing read schedules. If you simply want to do scheduled reads, you do not need to use this API. Please refer to [Read Actions](/read-actions). * **Platform API**: for advanced use cases in managing integrations and customer installations. Most customers do not use the Platform API directly, but interact with the Ampersand Dashboard, CLI or UI library instead. * **Proxy API**: for making direct API requests to your customer's SaaS instance without any transformations. Learn more in [Proxy Actions](/proxy-actions). > If you prefer exploring APIs interactively, check out the official Ampersand Postman Collection. > [Run In Postman](https://god.gw.postman.com/run-collection/29973051-00c27855-f4bf-48be-8730-a63a19aada9b?action=collection%2Ffork\&source=rip_markdown\&collection-url=entityId%3D29973051-00c27855-f4bf-48be-8730-a63a19aada9b%26entityType%3Dcollection%26workspaceId%3D4646d536-d0c0-45a7-a2ad-058b0eae9c2b#?env%5BProduction%5D=W3sia2V5IjoiYmFzZVVybCIsInZhbHVlIjoiaHR0cHM6Ly9hcGkud2l0aGFtcGVyc2FuZC5jb20vdjEiLCJ0eXBlIjoiZGVmYXVsdCIsImVuYWJsZWQiOnRydWV9LHsia2V5IjoicmVhZEJhc2VVcmwiLCJ2YWx1ZSI6Imh0dHBzOi8vcmVhZC53aXRoYW1wZXJzYW5kLmNvbS92MSIsInR5cGUiOiJkZWZhdWx0IiwiZW5hYmxlZCI6dHJ1ZX0seyJrZXkiOiJ3cml0ZUJhc2VVcmwiLCJ2YWx1ZSI6Imh0dHBzOi8vd3JpdGUud2l0aGFtcGVyc2FuZC5jb20vdjEiLCJ0eXBlIjoiZGVmYXVsdCIsImVuYWJsZWQiOnRydWV9LHsia2V5IjoiYXBpS2V5IiwidmFsdWUiOiIiLCJ0eXBlIjoic2VjcmV0IiwiZW5hYmxlZCI6dHJ1ZX0seyJrZXkiOiJwcm9qZWN0SWQiLCJ2YWx1ZSI6IiIsInR5cGUiOiJkZWZhdWx0IiwiZW5hYmxlZCI6dHJ1ZX0seyJrZXkiOiJpbnRlZ3JhdGlvbklkIiwidmFsdWUiOiIiLCJ0eXBlIjoiZGVmYXVsdCIsImVuYWJsZWQiOnRydWV9LHsia2V5IjoicHJvdmlkZXJBcHBJZCIsInZhbHVlIjoiIiwidHlwZSI6ImRlZmF1bHQiLCJlbmFibGVkIjp0cnVlfSx7ImtleSI6Imluc3RhbGxhdGlvbklkIiwidmFsdWUiOiIiLCJ0eXBlIjoiZGVmYXVsdCIsImVuYWJsZWQiOnRydWV9LHsia2V5Ijoib2JqZWN0TmFtZSIsInZhbHVlIjoiIiwidHlwZSI6ImRlZmF1bHQiLCJlbmFibGVkIjp0cnVlfSx7ImtleSI6Imdyb3VwUmVmIiwidmFsdWUiOiIiLCJ0eXBlIjoiZGVmYXVsdCIsImVuYWJsZWQiOnRydWV9XQ==) # Install and use the Ampersand CLI Source: https://docs.ampersand.ai/cli/overview Install the Ampersand CLI on macOS, Windows, or other systems, log in, deploy integrations from amp.yaml, and run it in CI/CD with an API key. The Ampersand Command Line Interface (CLI) enables you to deploy integrations and interact with the Ampersand platform. ## Installation ### For macOS using Homebrew To install the CLI using Homebrew (macOS users): ```bash theme={"dark"} brew tap amp-labs/cli brew update brew install amp-labs/cli/cli ``` #### Updating to new version If you already have the CLI installed, and want to upgrade to the latest version, run: ``` brew upgrade amp-labs/cli/cli ``` ### For Windows 1. Visit [Ampersand CLI Releases](https://github.com/amp-labs/cli/releases). 2. Download the appropriate binary for your machine’s architecture. 3. Extract the downloaded folder containing the `exe` file. 4. To use the CLI, open Command Prompt in the extracted folder: * Navigate to the folder in File Explorer * Click in the address bar * Type `cmd` and press Enter 5. Verify the installation by running: ```cmd theme={"dark"} amp ``` This should display a list of commands or options available in the CLI, confirming that it is properly configured and ready for use. 6. To make the CLI accessible from anywhere, add the folder to your `PATH`: * Open the Start menu * Search for "Environment Variables" * Click "Edit the system environment variables" * Click "Environment Variables" * Under "System variables", select "Path" and click "Edit" * Click "New" and add the path to the folder containing the `exe` file * Click "OK" on all windows ### For other systems 1. Visit [Ampersand CLI Releases](https://github.com/amp-labs/cli/releases). 2. Download the appropriate binary for your machine’s architecture. 3. Place the binary somewhere on your `PATH` (e.g. `/usr/bin`). **Note:**\ To ensure the CLI is accessible from anywhere, the binary must be placed in a directory listed in your `PATH`. You can inspect your current `PATH` using the following commands: * **Linux/macOS:** ```bash theme={"dark"} echo $PATH ``` ## Using the CLI ### Checking the version To check the version and build information of your CLI, run: ``` amp version ``` ### Log into your Ampersand account Ensure you have an Ampersand account. Create one on the [Ampersand Dashboard](https://dashboard.withampersand.com/sign-up) if you don't already have one. To log into the CLI: ``` amp login ``` To log out: ``` amp logout ``` ### Deploy integrations Once you have defined your integrations in an `amp.yaml` file, use the following commands to deploy: ```bash theme={"dark"} amp login amp deploy --project ``` ### View all commands To list all available commands and global flags: ```bash theme={"dark"} amp help ``` **Note:** For detailed explanations of each command and its options, refer to the [Command Reference](/cli/reference) section. ### Running in CI/CD Environments #### Github Action You can use the [official Ampersand Github Action](https://github.com/marketplace/actions/official-ampersand-github-action) to automatically deploy integrations from your repository. #### Generate an API key If you are using the official Github Action, or if you want to create your own CI/CD pipeline, you will need to generate an API key to run the CLI in a non-interactive mode that does not require a log in prompt. 1. Create an API key in the [Ampersand Dashboard](https://dashboard.withampersand.com/projects/_/api-keys). 2. Use the API key with the `key` flag. ``` amp list:integrations --key --project ``` Alternatively, you can set the API key as an environment variable: ``` export AMP_API_KEY= amp list:integrations --project ``` ### Debug mode For more detailed output, run commands with the `debug` flag. ``` amp login --debug ``` # CLI command reference Source: https://docs.ampersand.ai/cli/reference Syntax, flags, and examples for every Ampersand CLI command, from login and deploy to delete:integration, delete:installation, and the list commands. This section provides a detailed overview of the Ampersand CLI, including the available commands, their usage, and options. ## Usage The general syntax for using the Ampersand CLI is: ``` amp [command] [flags] ``` Where `[command]` is one of the available commands, and `[flags]` are parameters that modify the command's behavior. ## Global flags Global flags are options available for any Ampersand CLI command, allowing you to modify behavior or provide additional information consistently across all operations. Here are the available global flags: * `-d, --debug`: Enables debug logging mode (default: false). This flag is useful for troubleshooting as it provides more detailed output about the command's execution. * `-h, --help`: Displays help information for the command. This can be used to get more details about a specific command's usage and options. * `-k, --key string`: Specifies the Ampersand API key. This allows you to authenticate your CLI sessions using an API key instead of interactive login, which can be particularly useful in automated scripts or CI/CD pipelines. * `-p, --project string`: Specifies the Ampersand project name or ID. You can use `amp list:projects` to view available projects. You can use these flags by adding them after any command. For example: ``` amp list:integrations --debug --project my-project ``` This command would list integrations for the project "my-project" with debug logging enabled. ## Authentication commands These commands allow you to manage your session with the Ampersand CLI. Use these to log in to your Ampersand account before running other commands. As an alternative to logging in, you can also run commands using an API key. See [Running in CI/CD environments](/cli/overview#running-in-cicd-environments) for details. ### login Authenticates you against the CLI via an interactive browser flow. **Syntax:** ```bash theme={"dark"} amp login ``` **Example:** ```bash theme={"dark"} amp login ``` ### logout Logs out from your Ampersand account. **Syntax:** ```bash theme={"dark"} amp logout ``` ## Utility commands These commands provide general functionality to help you use and understand the Ampersand CLI. ### help Displays help information about any command. You can use `amp help` for general help and `amp help [command]` for command-specific help. **Syntax:** ```bash theme={"dark"} amp help [command] ``` **Example:** ```bash theme={"dark"} amp help deploy ``` **Notes:** * Provides detailed information about command usage and available options. ### version Displays the version of the Ampersand CLI installed on your system. **Syntax:** ```bash theme={"dark"} amp version ``` **Notes:** * Useful for verifying your CLI version or checking if updates are available. * Outputs the current version number of the CLI. **Example:** ```bash theme={"dark"} amp version ``` ## Managing integrations These commands are used to manage your integrations within Ampersand. They allow you to deploy changes to your integrations, delete integrations, and manage installations of integrations. ### deploy Deploys the integrations defined in the `amp.yaml` file to Ampersand. If an integration already exists, this command will apply any changes to the existing integration. **Syntax:** ```bash theme={"dark"} amp deploy --project [--key ] [--debug] ``` **Notes:** * Requires a folder containing an `amp.yaml` file. * Use this command to deploy new integrations or update existing ones. * Use this command when you've made changes to your integration **Example:** ```bash theme={"dark"} amp deploy ./integrations --project my-project ``` ### delete:integration Deletes a specific integration from your project. **Syntax:** ```bash theme={"dark"} amp delete:integration --project [--key ] [--debug] ``` **Notes:** * This action is irreversible. Use with caution. * Removes the integration & any associated installations from your project. **Example:** ```bash theme={"dark"} amp delete:integration f47ac10b-58cc-4372-a567-0e02b2c3d479 --project my-project ``` ### delete:installation Deletes a specific installation of an integration. **Syntax:** ```bash theme={"dark"} amp delete:installation --project [--key ] [--debug] ``` **Notes:** * This removes a specific installation of an integration, not the integration itself. * Requires both the integration ID and installation ID. **Example:** ```bash theme={"dark"} amp delete:installation 550e8400-e29b-41d4-a716-446655440000 91c38b0b-814c-4edd-8221-7e4f70a90123 --project my-project ``` ## Resource commands These commands help you view and manage various resources within your Ampersand projects. Use them to inspect available destinations, current installations, integrations, and projects associated with your account. ### list:destinations Lists all available destinations in your project. For more details on Destinations, refer to the [Ampersand Destinations](https://docs.ampersand.ai/destinations) Documentation. **Syntax:** ```bash theme={"dark"} amp list:destinations --project [--key ] [--debug] ``` **Notes:** * The output includes destination IDs and names. **Example:** ```bash theme={"dark"} amp list:destinations --project my-project ``` ### list:installations Displays all current installations of a given integration in your project. **Syntax:** ```bash theme={"dark"} amp list:installations --project [--key ] [--debug] ``` **Example:** ```bash theme={"dark"} amp list:installations 550e8400-e29b-41d4-a716-446655440000 --project my-project ``` ### list:integrations Provides a list of all integrations in your specified project. **Syntax:** ```bash theme={"dark"} amp list:integrations --project [--key ] [--debug] ``` **Example:** ```bash theme={"dark"} amp list:integrations --project my-project ``` ### list:projects Displays all projects associated with your Ampersand account. **Syntax:** ```bash theme={"dark"} amp list:projects [--key ] [--debug] ``` **Notes:** * Lists your projects to help identify available projects when using other commands. Note that this command only displays project information and does not provide interactive project selection. * Shows project IDs and names. **Example:** ```bash theme={"dark"} amp list:projects ``` # Concepts Source: https://docs.ampersand.ai/concepts Understand how Ampersand integrations, installations, revisions, connections, and configs fit together, and how a customer's config overrides a revision. Ampersand has one core idea: **you define an integration once, and each of your customers gets their own installation of it.** You describe what the integration can do in a manifest file. Each customer then connects their own account and chooses how much of that to enable. Ampersand stores those choices as a config and runs the integration against that customer's SaaS instance on their behalf. ```mermaid theme={"dark"} flowchart TD A["amp.yaml
what the integration can do"] -->|amp deploy| B["Revision
a versioned snapshot"] B --> C["Your customer connects
and picks objects and fields"] C --> D["Installation"] D --> E["Connection
their credentials"] D --> F["Config
what they chose"] B --> G["What actually runs
= revision, with config overrides applied"] F --> G ``` ## Integration An **integration** defines how your application interacts with a third-party SaaS tool. You have a single integration for every customer connecting to that provider. For example, one integration called `mySalesforceIntegration`, where Salesforce is the **provider**. You create an integration by defining it in a [manifest file](/manifest-reference) and deploying it with the [`amp` CLI](/cli/overview). The manifest describes what is *possible*, not what any particular customer has turned on. It can say: * objects and fields that are always read (`requiredFields`) * fields the customer may choose from (`optionalFields`, or `optionalFieldsAuto: all`) * fields the customer must map to their own schema (`mapToName`) * a schedule, a destination, and backfill behavior ## Installation An **installation** is one customer's instance of an integration. `mySalesforceIntegration` might have 30 installations, one per customer who has connected their Salesforce. An installation is created when a customer connects through an [embeddable UI component](/embeddable-ui-components), or when you call the [Create installation](/reference/installation/create-a-new-installation) endpoint. Each installation holds two things: The credentials for that customer's SaaS instance. Ampersand supports OAuth, API keys, and basic auth, and handles token refresh for you. That customer's choices, stored as overrides on the integration's definition: fields, mappings, schedule, and destination. ## Revision Every time you change `amp.yaml` and run `amp deploy`, Ampersand stores a new **revision**, a versioned snapshot of the integration's definition. Revisions matter because your customers are not all on the same version at the same moment. A config records the revision it was created or last updated against, so you can tell which definition a given customer is running. When Ampersand renders the field-mapping UI, it requests a **hydrated revision**: the revision enriched with live field metadata from that customer's connected instance. It carries the fields that actually exist in their account, with display names and types. That is how the UI can offer a customer *their* custom fields, not a generic list. ## Config The **config** records one customer's choices for an installation. It is not a complete description of what runs. It is a set of **overrides on top of the revision**. Anything the config specifies wins for that installation. Anything it leaves unset falls back to what the integration's revision defines. A config is a diff, not a replacement. This is why two customers on the same integration can behave differently without you maintaining two manifests, and why a customer who has changed nothing simply follows what you declared. ### What a config can override For a read object, a config can set the fields the customer selected (`selectedFields`, or `selectedFieldsAuto: all`), the mappings they chose (`selectedFieldMappings`, `selectedValueMappings`), and operational settings: `schedule`, `destination`, `backfill`, `fieldFilters`, and `disabled` to pause an object entirely. For a write object it can set `deletionSettings`, `selectedFieldSettings`, and `selectedValueDefaults`. ### A worked example Suppose your manifest declares: ```yaml theme={"dark"} - objectName: contact destination: contactsWebhook schedule: "*/15 * * * *" requiredFields: - fieldName: email - mapToName: lifecycle_stage optionalFieldsAuto: all ``` A customer installs it and maps `lifecycle_stage` to their `hs_lead_status` field, adds two optional fields, and leaves everything else alone. Their config carries only those choices. What actually runs for them is the merge: `email` and their two optional fields are read, `hs_lead_status` is mapped to `lifecycle_stage`, and the schedule and destination come from your revision, because the config never mentioned them. If that customer later asks for hourly syncs, you set `schedule` on their config. It now overrides yours, for them alone. Every other installation still follows the manifest. You can read a config with [Get an installation](/reference/installation/get-an-installation), or from the SDK via `installation.config`, and change one with [Update an installation](/reference/installation/update-an-installation). ## How they work together `amp deploy` creates a revision from your `amp.yaml`. Nothing runs yet. You have only described what is possible. They authenticate against their own SaaS instance through your embedded UI. Ampersand stores those credentials as a connection. Ampersand hydrates the revision with the live schema from their instance, and the UI offers the fields your manifest allows. Their selections become the config. Scheduled reads, writes, and subscriptions execute against that customer's instance, using your revision with their config's overrides applied, and deliver results to your destination. ### What this means in practice * **One integration, many configs.** Two customers on the same integration can read different fields, because each customer's config decides what's included. * **A config only carries what the customer changed.** Everything else follows your manifest, so defaults stay in one place. * **To change one customer, update their config.** To change the default for everyone, change the manifest and deploy. * **Mapped fields need a customer decision.** A `mapToName` entry cannot resolve until someone tells Ampersand which of their fields it corresponds to. ## Related terms A **provider** is the third-party API you integrate with. A **provider app** is the OAuth app you register with that provider, stored in Ampersand so customers can authenticate against it. A **destination** is where read results are delivered: a webhook, warehouse, or queue. For the full glossary, see [Terminology](/terminology). # BigQuery Source: https://docs.ampersand.ai/customer-guides/bigquery Connect a BigQuery dataset to an integration: enable APIs, create a GCP service account and JSON key, gather connection details, and fix access issues. This guide walks you through connecting your Google BigQuery dataset to an integration using a GCP service account. ## Before installing a BigQuery integration You will need access to a Google Cloud Platform (GCP) project that contains the BigQuery dataset you want to connect. You must have sufficient permissions to create service accounts and manage IAM roles (typically **Owner** or **Editor** access). ### 1. Enable the required APIs 1. Go to the [Google Cloud Console](https://console.cloud.google.com/). 2. Select your project from the project picker at the top of the page. 3. Navigate to **APIs & Services** > **Enabled APIs & Services**. 4. Search for **BigQuery API** and verify it is enabled. If not, click **Enable**. 5. Also search for and enable the **BigQuery Storage API**, as this is required for high-performance data reads. ### 2. Create a service account A service account is a special-purpose GCP account used by applications to access resources. 1. In the Google Cloud Console, navigate to **IAM & Admin** > **Service Accounts**. 2. Click **Create Service Account**. 3. Enter a name (e.g., "Integration Service Account") and an optional description. 4. Click **Create and Continue**. 5. Grant the following roles: * **BigQuery Data Viewer** — allows reading table data and metadata. * **BigQuery Job User** — allows running queries in the project. 6. Click **Done**. If you only want to grant access to a specific dataset rather than the entire project, you can assign the **BigQuery Data Viewer** role at the dataset level instead. Navigate to the dataset in the BigQuery console, click **Sharing** > **Permissions**, and add the service account there. ### 3. Create and download a key 1. From the **Service Accounts** page, click on the service account you just created. 2. Go to the **Keys** tab. 3. Click **Add Key** > **Create new key**. 4. Select **JSON** as the key type and click **Create**. 5. A JSON file will be downloaded. Store it securely. This file contains credentials that grant access to your BigQuery data. You will need to base64-encode the contents of this file before providing it during installation. Use the command that matches your environment: **macOS (Terminal)** ```bash theme={"dark"} base64 -i service-account-key.json ``` **Linux, Git Bash, or WSL** ```bash theme={"dark"} base64 -w 0 service-account-key.json ``` **Windows (PowerShell)** ```powershell theme={"dark"} [Convert]::ToBase64String([IO.File]::ReadAllBytes((Resolve-Path 'service-account-key.json'))) ``` Copy the encoded string (a long single line). You will paste it when installing the integration. ### 4. Gather your connection details You will need the following information when installing the integration: | Input | Where to find it | | - | - | | **Service Account Key** | The base64-encoded contents of the JSON key file from step 3. | | **GCP Project ID** | Your project's ID (not the project number). Find it at the top of the Google Cloud Console or under **IAM & Admin** > **Settings**. It looks like `my-project-id`, not a number. | | **Dataset Name** | The name of the BigQuery dataset you want to connect. Find it in the BigQuery console under your project in the left sidebar. | | **Timestamp Column** | The name of a `TIMESTAMP` or `DATETIME` column in your tables that tracks when rows were last modified (e.g., `updated_at`, `modified_date`). This is used for incremental syncing. | ## Installing the integration When you are installing the integration, you will be prompted for the inputs listed above. After providing them, the integration will begin syncing data from your BigQuery dataset. ## Troubleshooting ### Access blocked despite valid credentials If your GCP project is protected by [VPC Service Controls](https://cloud.google.com/vpc-service-controls/docs/overview), all external API requests are denied by default, including the BigQuery Storage Read API used by this integration. Ask your GCP administrator to create an ingress rule that allows the service account to access `bigquery.googleapis.com` from outside the perimeter. ### Missing or filtered data If columns in your tables use [column-level security](https://cloud.google.com/bigquery/docs/column-level-security-intro) (Data Catalog policy tags), the service account also needs the `Fine-Grained Reader` role on those policy tags. If your tables use [row-level security](https://cloud.google.com/bigquery/docs/row-level-security-intro), rows may be silently filtered. Please ensure the service account is included in the relevant row access policies. # Gong Source: https://docs.ampersand.ai/customer-guides/gong Create a Gong automation rule that sends call.created webhooks to your developer's webhook URL, and learn when to expect each webhook after a call ends. ## Checklist before you start Confirm the following: * You are a Gong **Technical Administrator** (the role required to create automation rules and configure outbound webhooks). * Your developer has provided you with a **webhook URL** in this format: ``` https://subscribe-receiver.withampersand.com/zt1secx7jknlhb/v1/projects/PROJECT_ID/integrations/INTEGRATION_ID/installations/INSTALLATION_ID ``` ## Set up webhooks for real-time updates (Subscribe Actions) To send `call.created` events to your developer's application, you'll create an **automation rule** in Gong that fires a webhook each time a call is processed. 1. Sign in to Gong and open **Admin Settings** in the left sidebar. 2. In the left sidebar, navigate to **Automation rules**. 3. Click **Add rule**. 4. (Optional) Add any filters you'd like to apply — for example, restrict the rule to specific participants. Leave the filters empty to send every new call. 5. Paste the webhook URL your developer gave you into the **Enter URL** field. 6. Give the rule a descriptive name and toggle **"Rule Status"** to **Enabled**. 7. (Optional) Send a test webhook and click Save. ## When to expect the webhook to fire Gong fires the `call.created` webhook after a call has finished processing and appears in your Gong call library, which can take **several hours** after the call ends. You will not see events arrive immediately after a call wraps up — this is expected. The screenshot below shows where a processed call shows up. Once a call reaches this state, the webhook has been (or will shortly be) sent. ## For official setup details, see: * [Gong Automation Rules overview](https://help.gong.io/docs/introduction-to-automation-rules) * [Send a webhook from an automation rule](https://help.gong.io/docs/create-a-webhook-rule) # Google (workspace delegation) Source: https://docs.ampersand.ai/customer-guides/google-workspace-delegation Connect your Google Workspace domain with a GCP service account using domain-wide delegation for Gmail, Calendar, and Contacts, and fix common auth errors. This guide walks you through connecting your Google Workspace domain to an integration that uses a GCP service account with [domain-wide delegation](https://developers.google.com/identity/protocols/oauth2/service-account#delegatingauthority). This is a headless, server-to-server authentication method — there is no interactive login, and the integration accesses Gmail, Calendar, and Contacts data on behalf of users in your domain. ## Before installing a Google (Workspace Delegation) integration You will need: * **Google Cloud Platform (GCP)** access with permission to create service accounts (typically **Owner** or **Editor** on a GCP project). * **Google Workspace super-admin** access to your domain (required to authorize domain-wide delegation). ### 1. Enable the required APIs 1. Go to the [Google Cloud Console](https://console.cloud.google.com/). 2. Select (or create) a GCP project that will own the service account. 3. Navigate to **APIs & Services** > **Library**. 4. Enable the APIs your integration needs: * **Gmail API** for Gmail integrations. * **Google Calendar API** for Calendar integrations. * **People API** for Contacts integrations. ### 2. Create a service account 1. In the Google Cloud Console, navigate to **IAM & Admin** > **Service Accounts**. 2. Click **Create Service Account**. 3. Enter a name and an optional description. 4. Click **Create and Continue**, then **Done**. You do not need to grant any project-level IAM roles for domain-wide delegation as the authorization happens in the Workspace Admin console in step 4. ### 3. Create and download a key 1. From the **Service Accounts** page, click on the service account you just created. 2. Note the **Unique ID** (a numeric client ID, e.g. `123456789012345678901`). You will need this in the next step. 3. Go to the **Keys** tab. 4. Click **Add Key** > **Create new key**. 5. Select **JSON** as the key type and click **Create**. 6. A JSON file will be downloaded. Store it securely — this file contains credentials that can impersonate any user in your Workspace domain once delegation is authorized. You will need to base64-encode the contents of this file before providing it during installation. Use the command that matches your environment: **macOS (Terminal)** ```bash theme={"dark"} base64 -i service-account-key.json ``` **Linux, Git Bash, or WSL** ```bash theme={"dark"} base64 -w 0 service-account-key.json ``` **Windows (PowerShell)** ```powershell theme={"dark"} [Convert]::ToBase64String([IO.File]::ReadAllBytes((Resolve-Path 'service-account-key.json'))) ``` Copy the encoded string (a long single line). You will paste it when installing the integration. ### 4. Authorize domain-wide delegation This step grants the service account permission to impersonate users in your Workspace domain. It must be performed by a **super-admin** of the Google Workspace domain. 1. Go to the [Google Admin console](https://admin.google.com/). 2. Navigate to **Security** > **Access and data control** > **API controls**. 3. Click **Manage Domain Wide Delegation**. 4. Click **Add new**. 5. In **Client ID**, paste the numeric **Unique ID** of the service account from step 3. 6. In **OAuth scopes**, enter a comma-separated list of the scopes your integration needs. For example: * Gmail (read/write): `https://mail.google.com/` * Gmail (read-only): `https://www.googleapis.com/auth/gmail.readonly` * Calendar (read/write): `https://www.googleapis.com/auth/calendar` * Calendar (read-only): `https://www.googleapis.com/auth/calendar.readonly` * Contacts (read/write): `https://www.googleapis.com/auth/contacts` * Contacts (read-only): `https://www.googleapis.com/auth/contacts.readonly` 7. Click **Authorize**. Changes to domain-wide delegation can take up to 24 hours to propagate, though most take only a few minutes. If the integration returns `unauthorized_client` errors immediately after installation, wait and retry. ### 5. Gather your connection details You will need to share the following information with the integration builder: | Input | Where to find it | | - | - | | **Service Account Key** | The base64-encoded contents of the JSON key file from step 3. | | **User Emails** | A full list of email addresses of all the users whose data the integration should access (e.g. `user1@company.com, user2@company.com`). | | **Scopes** | The exact list of OAuth scopes you authorized in step 4 (or a subset). | # Housecall Pro Source: https://docs.ampersand.ai/customer-guides/housecallPro Generate a Housecall Pro API key as an Admin on a MAX subscription, and optionally set up webhooks with your developer's URL for real-time updates. ## Checklist before you start Confirm the following: * You are a Housecall Pro **Admin** user. * Your Housecall Pro account has a **MAX subscription**. ## 1. Generate your Housecall Pro API key Follow these steps to generate your Housecall Pro API key. 1. Click the **My Apps** icon (3x3 grid) in the top-right corner. 2. Click **Go to App Store**. 3. Under **My Apps**, find and open the **API Key Management** app card. 4. Click **Generate API Key**. 5. Enter a descriptive name for the key. 6. Copy the API key and store it securely. ## 2. Set up webhooks for real-time updates (Subscribe Actions) Skip this section if your developer did not give you a webhook URL, or if you do not need real-time updates. Follow these steps to set up webhooks in Housecall Pro. 1. Click the **My Apps** icon (3x3 grid) in the top-right corner. 2. Click **Go to App Store**. 3. Under **All apps**, find and open **Webhooks**. 4. Set Webhooks to **Active**, paste the link the developer gave you into the **Webhook URL** field, then click **Save**. 5. Enable the webhook events that you want to subscribe to, then click **Save**. **`pro.created`** in Housecall Pro means a new **employee**; the connected product may display it as **`employee.created`**. ## For official setup details, see: * [Housecall Pro API Overview](https://help.housecallpro.com/en/articles/8505035-api-overview#h_3b0cd3e648) * [Housecall Pro Webhooks Setup](https://help.housecallpro.com/en/articles/5683520-how-to-enable-webhooks) # HubSpot Source: https://docs.ampersand.ai/customer-guides/hubspot Check HubSpot requirements before installing an integration: you must be a Super Admin, and you need a Sales Hub seat if the integration accesses Leads. ## Checklist before installing a HubSpot integration We recommend verifying the following requirements before installing the HubSpot integration to ensure a smooth setup. ### 1. Ensure you are a HubSpot Super Admin You must be a **Super Admin** to install an app. This is a [HubSpot requirement](https://developers.hubspot.com/docs/apps/developer-platform/build-apps/authentication/oauth/oauth-quickstart-guide). ### 2. Ensure you have a Sales Hub seat (if access to Leads is required) If your integration requires access to Leads, you must have a Sales Hub seat in HubSpot. Without this, you may not be able to retrieve or manage lead data. To check if you have a Sales Hub seat: 1. Log in to *HubSpot*. 2. Click on your profile picture and select **Settings**. 3. In the left sidebar, navigate to **Users & Teams**. 4. Locate your user account and check for **Sales Hub seat** assignment. 5. If you do not have a Sales Hub seat, ask your HubSpot administrator to assign one to you. Learn more about different HubSpot seats [here](https://knowledge.hubspot.com/account-management/manage-seats). # Loxo Source: https://docs.ampersand.ai/customer-guides/loxo Find the metadata values you need before installing a Loxo integration: your full domain from the browser URL and your Agency Slug from workspace settings. ## Checklist before installing a Loxo integration Before beginning the installation, you’ll need to gather a few metadata values from your Loxo account. The steps below outline where to locate each of these values. ### 1. Full Domain Log in to your Loxo account and check the URL in your browser’s address bar. It typically follows this pattern: `https://[your-domain-here]`. loxo-full-domain ### 2. Agency Slug Go to **Settings > Workspace > General**. In the **Agency Info** section, check the **Resume Forwarding Address**. The text before the '@' symbol is your **Agency Slug**. loxo-agency-slug # Marketo Source: https://docs.ampersand.ai/customer-guides/marketo Prepare Marketo for an integration by creating an API-only role, an API-only user, and a LaunchPoint custom service, then find your Munchkin Account ID. ## Checklist before installing a Marketo integration We recommend completing the following steps before installing the Marketo integration to ensure a smooth setup. ### 1. Create an API-Only Role 1. Log in to **Adobe Experience**. 2. Click on **Marketo Engage** and launch your instance. 3. At the top, navigate to **Admin**. 4. In the left sidebar, go to **Admin** → **Security** → **Users & Roles**. 5. Click on **Roles**, then **New Role**, and provide the API role name (used in Step 2). 6. Select the appropriate API access permissions for this role.\\ Img 7. Click **Create**. For more information, refer to [Marketo's official documentation](https://experienceleague.adobe.com/en/docs/marketo/using/product-docs/administration/users-and-roles/create-an-api-only-user-role). ### 2. Create an API-Only User 1. Log in to **Adobe Experience**. 2. Click on **Marketo Engage** and launch your instance. 3. At the top, navigate to **Admin**. 4. In the left sidebar, go to **Admin** → **Security** → **Users & Roles**. 5. Click on **Users**, then **Create API Only User**, and fill in the required details. 6. Under **Roles**, select the role created in Step 1.\\ Img 7. Click **Create**. For more information, refer to [Marketo's official documentation](https://experienceleague.adobe.com/en/docs/marketo/using/product-docs/administration/users-and-roles/create-an-api-only-user). ### 3. Create a Custom Service 1. Log in to **Adobe Experience**. 2. Click on **Marketo Engage** and launch your instance. 3. At the top, navigate to **Admin**. 4. In the left sidebar, go to **Admin** → **Integration** → **LaunchPoint**. 5. Click on **New** → **New Service**, and provide the following details: * **Display Name**: (Your preference) * **Service**: Custom * **Description**: (Your preference) * **API Only User**: Select the user created in Step 2\\ Img 6. Click **Create**. 7. After creation, you can view the **Client ID** and **Client Secret** by clicking **View Details**.\\ Img 8. You will also need the `workspaceId` (Munchkin Account ID). This can be found under **Admin** → **Integration** → **Munchkin**.\\ Img For more information, refer to [Marketo's official documentation](https://experienceleague.adobe.com/en/docs/marketo/using/product-docs/administration/additional-integrations/create-a-custom-service-for-use-with-rest-api#_blank). # NetSuite Source: https://docs.ampersand.ai/customer-guides/netsuite Connect NetSuite with OAuth 2.0 Client Credentials: install the bundle, create an integration record, generate a certificate, and map it to a role. This guide walks you through connecting your NetSuite account to an integration using OAuth 2.0 Client Credentials (Machine to Machine). ## Before installing a NetSuite integration You will need **Administrator access** to your NetSuite account. ### 1. Install the bundle 1. Navigate to **Customization** > **SuiteBundler** > **Search & Install Bundles**. 2. Search for the bundle name or ID. 3. Select the bundle from the results and click **Install**. 4. Wait for the installation to complete. This may take a few minutes. The bundle will install SuiteScripts and custom objects (fields, records, roles, etc) into your NetSuite account. ### 2. Verify the deployment 1. Navigate to **Customization** > **Scripting** > **Script Deployments**. 2. Find the RESTlet deployment installed by the bundle and click on 'View'. 3. Verify the deployment **Status** is **Released**. 4. Click on the name of the script, then note the **URL** — you will need it for the installation step. ### 3. Enable required features 1. Log in to *NetSuite* as an Administrator. 2. Navigate to **Setup** > **Company** > **Enable Features** > **SuiteCloud**. 3. Under **Manage Authentication**, check **OAuth 2.0**. 4. Under **SuiteScript**, check **Server SuiteScript**. 5. Under **Manage Authentication** (or **SuiteTalk**), check **REST Web Services**. 6. Make sure that the following features are also enabled in your environment: * Accounting * Custom Records * File Cabinet 7. Click **Save**. ### 4. Create an integration record The integration record identifies the external application that will connect to your NetSuite account. 1. Navigate to **Setup** > **Integration** > **Manage Integrations** > **New**. 2. Enter a **Name** for the integration (e.g. "Ampersand Integration"). 3. Under the **Token-based Authentication** section: * Uncheck **TBA: Authorization Flow** * Uncheck **Token-Based Authentication** 4. Under the **OAuth 2.0** section: * Uncheck **Authorization Code Grant** * Check **Client Credentials (Machine to Machine) Grant** 5. Under **Scope**, check the following: * **RESTlets** * **REST Web Services** 6. Click **Save**. 7. After saving, NetSuite displays the **Consumer Key / Client ID** under the **Client Credentials** section. Copy this value and store it securely — it is shown only once and cannot be retrieved later. ### 5. Generate a certificate key pair In this step you'll use your computer's terminal to create two files: a private key and a self-signed certificate. This is a one-time operation — if anything goes wrong, you can delete the files and start over. Before you begin, you need **OpenSSL** available in your terminal. macOS and most Linux distributions include it by default. On **Windows**, use **Git Bash** (bundled with [Git for Windows](https://gitforwindows.org/), which includes OpenSSL), **WSL**, or install OpenSSL and ensure it is on your `PATH`. #### a. Create a folder and open a terminal in it Create a new folder anywhere you'll remember — your Desktop works fine. For example, create one called `netsuite-keys`. Then open a terminal *inside that folder* so the files you generate end up there: * **macOS**: In **Finder**, right-click the folder and choose **New Terminal at Folder**. (If you don't see this option, enable it under **System Settings** > **Keyboard** > **Keyboard Shortcuts** > **Services** > **Files and Folders** > **New Terminal at Folder**.) * **Windows**: In **File Explorer**, right-click inside the folder and choose **Open in Terminal** or **Open Git Bash here**. * **Any OS (fallback)**: Open your terminal app, type `cd ` (with a trailing space), drag the folder from your file browser onto the terminal window, and press **Enter**. #### b. Run the OpenSSL commands Copy each command below, paste it into the terminal right after the prompt (`%` on macOS, `$` on Linux/Git Bash, `PS>` on PowerShell), and press **Enter**. Run them one at a time. ```bash theme={"dark"} openssl ecparam -name prime256v1 -genkey -noout -out private-key.pem ``` ```bash theme={"dark"} openssl req -new -x509 -key private-key.pem -out cert.pem -days 730 ``` The second command will prompt you for certificate details (country, organization name, email, etc.). **Press Enter at every prompt to accept the defaults** — these values are not used by the integration, so it doesn't matter what they are. #### c. Confirm the two files were created If the commands succeeded, your folder now contains two files: * `private-key.pem` — your private key. You'll base64-encode it in the next sub-step. * `cert.pem` — your public certificate. You'll upload it to NetSuite in step 7. Open the folder in **Finder** (macOS) or **File Explorer** (Windows) to confirm both files are there. If you can't see them, search your computer for `cert.pem` — the files were created in whichever folder your terminal was open in. If anything looks wrong — missing files, error messages, accidentally hit Enter too many times — just delete the files and run the commands again. The keys aren't registered anywhere until you upload `cert.pem` to NetSuite in step 7, so there's no risk in starting over. #### d. Base64-encode the private key Run the command below to print a base64-encoded version of your private key directly in the terminal. Use the version that matches your environment: **macOS (Terminal)** ```bash theme={"dark"} base64 -i private-key.pem ``` **Linux, Git Bash, or WSL** ```bash theme={"dark"} base64 -w 0 private-key.pem ``` **Windows (PowerShell)** ```powershell theme={"dark"} [Convert]::ToBase64String([IO.File]::ReadAllBytes((Resolve-Path 'private-key.pem'))) ``` After running the command, you should see a long single-line string printed in the terminal, starting with `LS0tLS1CRUdJTiBFQyBQUklWQVRFIEtFWS0tLS0t...`. Select and copy the entire string — you'll paste it during the installation process. ### 6. Assign the integration role to a user or create a new user The bundle will include a custom role with the permissions needed for the integration. You need to assign this role to the employee/user account that the integration will run as. 1. Navigate to **Setup** > **Users/Roles** > **User Management** > **Employees** (or **Lists** > **Employees**). 2. Find and edit the employee record you want the integration to run as. This can be an existing user or a dedicated integration user. 3. Go to the **Access** tab. 4. In the **Roles** subtab, click **Add**. 5. Select the custom role from the dropdown. 6. Click **Save**. If you prefer to use a dedicated integration user rather than an existing employee, create a new user/employee record first, then assign the role as described above. This is recommended for security purposes. ### 7. Create a machine-to-machine certificate mapping 1. Navigate to **Setup** > **Integration** > **OAuth 2.0 Client Credentials (M2M) Setup**. 2. Click **Create New**. 3. Configure the following fields: * **Entity**: Select the employee/user account the integration will run as (from the previous step) * **Role**: Select the custom role that the bundle installed. Please note that the role must include the **Log in Using OAuth 2.0 Access Tokens** permission. * **Application**: Select the integration record you created in step 4. * **Certificate**: Upload the `cert.pem` file you generated in step 5. 4. Click **Save**. 5. Copy the **Certificate ID** from the list — you will need this value when installing the integration. Check that the `Valid Until` date for the certificate is two years in the future. NetSuite allows a maximum validity of 730 days. ## Installing the integration When you are installing the integration, you will be prompted for a number of inputs: Here is where to find each value: | Input | Where to find it | | - | - | | **Client ID** | The Consumer Key from step 2. | | **Certificate ID** | From step 4. | | **Private Key** | Base64-encoded contents of `private-key.pem` (from step 3) | | **Account ID** | Your NetSuite URL (e.g. `https://1234567.app.netsuite.com` → Account ID is `1234567`), or **Setup** > **Company** > **Company Information**. | | **RESTlet Deployment URL** | This is printed out in the Deployments tab of the script (see previous step), and looks like `/app/site/hosting/restlet.nl?script=3045&deploy=1`. | **Sandbox accounts**: If you are connecting a sandbox account, your Account ID may contain an underscore and suffix (e.g. `1234567_SB1`). Provide it exactly as shown in your NetSuite URL or Company Information page. # Ampersand customer guides for integration setup Source: https://docs.ampersand.ai/customer-guides/overview Learn what the Ampersand customer guides offer end users setting up integrations: step-by-step instructions, troubleshooting tips, and prerequisites. Welcome to the Ampersand **Customer Guides** section! Customer guides are specifically designed for end users who are setting up or troubleshooting integrations built with Ampersand. While our Provider Guides are intended for developers building integrations, these guides focus on clear, step-by-step instructions for anyone—whether or not they have a technical background. With the customer guides, we want to simplify the setup process for you. Each guide covers common setup steps, troubleshooting tips, and best practices to ensure a smooth and hassle-free experience. ## What you'll find here * Step-by-step instructions for common integration platforms * Troubleshooting tips for common issues * Prerequisites checklists before installation * Visual aids including screenshots and GIFs to guide you through complex processes # Salesforce Source: https://docs.ampersand.ai/customer-guides/salesforce Set up your Salesforce org for an integration using an External Client App or Connected App, with the user permissions and OAuth policies it requires. This guide helps you set up your Salesforce org to work with an Ampersand-powered integration. The steps differ depending on whether the integration uses an **External Client App** or a **Connected App**. If you're not sure which one you're using, ask the developer who built the integration. ## Checklist before installing Complete these checks before you install or connect the integration. If any of them fail, the connection will not work. ### 1. Confirm your edition has API access Your Salesforce edition must include API access. Without it, the integration cannot connect. Check [Salesforce's API access by edition](https://help.salesforce.com/s/articleView?language=en_US\&id=000385436\&type=1). If your edition does not include API access, contact your Salesforce account representative to upgrade. ### 2. Know which app type you're installing Ask the developer whether this integration uses an **External Client App** or a **Connected App**. Use the matching tab in the sections below. ### Glossary * **License** — Purchased from Salesforce; sets the maximum permissions a user can receive. Which license you need depends on who connects — see [Choose credentials and grant access](#choose-credentials-and-grant-access). * **Profile** — Controls object, field, and feature access. Each user has exactly one profile (standard or custom), tied to one license. System Administrator is a profile, not a license. * **Permission set** — Extra permissions assigned on top of a profile. Users can have multiple permission sets; each set is tied to a license. ## Setting up the integration ### Install the package The developer will provide a **package install URL**. Complete this install **before** connecting Salesforce in the integration UI, or OAuth will fail. 1. Open the package install URL provided by the developer. 2. Log in to your Salesforce org if prompted. 3. Select **Install for All Users**. 4. Check the acknowledgment checkbox. 5. Click **Install**. You will see a progress screen while the installation completes. Once complete, you should see a confirmation screen: To verify, go to **Setup**, search for **Installed Packages**, and confirm the package appears in the list. ### Configure token policy settings Set the refresh token policy so the integration stays connected after access tokens expire. 1. Log in to **Salesforce**. 2. Go to **Setup**. 3. In the **Quick Find** box, search for **Connected Apps**. 4. Click **Manage Connected Apps**. 5. Find and click the name of the application you are integrating with. 6. Scroll to **OAuth Policies**. 7. Under **IP Relaxation**, select **Relax IP restrictions**. 8. Set **Refresh Token Policy** to **Refresh token is valid until revoked**. Refresh Token Settings 9. Click **Save**. After you connect the app, also confirm **Permitted Users** under app policies. See [After installing the integration](#after-installing-the-integration). **IP Relaxation** applies to the IP address you sign in from when you authorize the app, not to the IP addresses the integration makes API calls from. If you want to limit where the integration's API calls can come from, see [Restrict API access by IP address](#restrict-api-access-by-ip-address). ## Choose credentials and grant access Pick one way to connect, then grant the permissions below. ### Required system permissions The following system permissions need to be enabled on the profile or permission set you configure. The sections below provide instructions on how to enable them. * **API Enabled** * If the integration includes real-time Subscribe Actions, also enable the [Subscribe Action permissions](#subscribe-action-permissions) * If the integration creates or updates custom fields, also enable the [custom field management permissions](#custom-field-management-permissions) * For Connected Apps only, also enable one of: * **Use Any API Client** (shown if your org has [API Access Control](https://help.salesforce.com/s/articleView?id=xcloud.security_api_access_control_about.htm\&type=5) enabled) * **Approve Uninstalled Connected Apps** (if you do not see **Use Any API Client**) ### Option A: Human user Use one of the following: * **System Administrator** — Already on a supported license (System Administrator is a profile on the **Salesforce** license). You still need to grant the correct object and field permissions; they are not always included by default. * **Sales team member or other human user** — Must have the **Salesforce** User License. The **Salesforce Platform** User License is insufficient. This user can have a standard profile (such as **Standard User**) or a custom profile. #### 1. Configure system permissions In **Setup**, search for **Profiles** in the **Quick Find** box and open it. Then: 1. Select the profile you'd like to view and edit. 2. Click **Edit** at the top of the page. Edit profile 3. Ensure the checkboxes for the [required system permissions](#required-system-permissions) are checked. 4. Click **Save**. #### 2a. Field permissions for a standard profile If the user has a standard profile (such as **Standard User**): 1. Click the gear icon in the top-right corner and select **Setup**. 2. In the left-hand search bar, type **Object Manager** and open it. Setup Object Manager 3. Choose the object you need (for example, **Account**), then select **Fields & Relationships** from the left navbar. Setup Object Manager Account 4. Find the field you want to adjust and click it. 5. Click **Set Field-Level Security**. Set Field Level Security 6. Ensure **Visible** is selected for the profile. If the profile is not in the list, it does not have access to the object and that cannot be changed here. Check Visible for profile 7. Repeat steps 4–6 for all fields the integration needs to read, especially custom fields. #### 2b. Object and field permissions for a custom profile If the user has a custom profile: 1. Click the gear icon in the top-right corner and select **Setup**. 2. In the left-hand search bar, type **Object Manager** and open it. Setup Object Manager 3. Choose the object you need (for example, **Account**) and go to **Object Access** in the left navbar. Select the **Profiles** tab, click **Edit**, and grant the necessary permissions for your custom profile. * If the integration needs to read data, ensure **Read**, **View All Records**, and **View All Fields** are checked. * If the integration needs to write data, ensure all boxes are checked. Edit Object Access ### Option B: Salesforce Integration user Use a **Salesforce Integration** user when you want an API-only identity with no Salesforce UI access. Create the user with the **Salesforce Integration** license (steps below). #### 1. Create a new user 1. Click the gear icon in the top-right corner and select **Setup**. 2. In the left-hand search bar, type **Users** and open it. 3. Create a new user: * For **User License**, select **Salesforce Integration**. * For **Profile**, select **Minimum Access - API Only Integrations**. #### 2. Create a permission set 1. Click the gear icon in the top-right corner and select **Setup**. 2. In the left-hand search bar, type **Permission Sets** and open it. 3. Click **New**. 4. Create the permission set: * Name it something general (for example, `Integration User Permission Set`) or describe the access (for example, `Account and Contact Access`). * In the **License** dropdown, select **Salesforce API Integration**. #### 3. Configure object permissions 1. Click **Object Settings**. 2. For each object the integration needs, open it (for example, **Accounts**). 3. Check the relevant boxes under **Object Permissions** and **Field Permissions**, then click **Save**. 4. Repeat for every object the integration needs to access. #### 4. Configure system permissions 1. Select **System Permissions**. 2. Ensure the [required system permissions](#required-system-permissions) are selected, then click **Save**. #### 5. Assign the permission set to the integration user 1. Click **Manage Assignments**. 2. Click **Add Assignment**. 3. Select the integration user you created in step 1. 4. Set **Expires On** to **Never Expires**, then click **Assign**. ## After installing the integration Configure OAuth policies so the integration can maintain a stable connection. 1. Log in to **Salesforce**. 2. Go to **Setup**. 3. In the **Quick Find** box, search for **External Client App Manager**. 4. Click the name of the installed External Client App. 5. Go to the **Policies** tab and click **Edit**. 6. Under **OAuth Policies**, set **Permitted Users** to **All users may self-authorize**. 7. Under **App Authorization**: * Set **Refresh Token Policy** to **Refresh token is valid until revoked**. * Set **IP Relaxation** to **Relax IP restrictions**. 8. Click **Save**. **IP Relaxation** applies to the IP address you sign in from when you authorize the app, not to the IP addresses the integration makes API calls from. If you want to limit where the integration's API calls can come from, see [Restrict API access by IP address](#restrict-api-access-by-ip-address). Configure ECA OAuth Policies Confirm **Permitted Users** so the right people can authorize the app. 1. Log in to **Salesforce**. 2. Go to **Setup**. 3. In the **Quick Find** box, search for **Connected Apps OAuth Usage**. 4. Find this integration's connected app and click **Manage App Policies**. 5. Under **OAuth Policies**, set **Permitted Users** to one of: * **Admin approved users are pre-authorized** — only selected users can access. * **All users may self-authorize** — all users can access the app. Permitted Users If you need to change these settings: 1. Click **Install** next to your connected app. 2. In **OAuth Usage and Policies**, set the appropriate permissions level. 3. Click **Save**. ## Restrict API access by IP address This section is optional. It restricts the addresses that the integration's Salesforce user is allowed to log in from. All API requests the integration makes to your Salesforce org originate from a [fixed set of Ampersand IP addresses](/security/outgoing-ip-addresses), so you can limit that user to those addresses plus your own. Two settings are easy to mistake for this one: * **IP Relaxation** on the app applies to the IP address your browser connects from when you authorize the integration, not to the addresses the integration makes API calls from. Leave it set to **Relax IP restrictions**. * **Trusted IP Ranges** under **Setup** → **Security** → **Network Access** are not a restriction. Logins from those addresses skip identity verification, but logins from other addresses are still allowed, so listing Ampersand's addresses there does not block a stolen token. The control that actually restricts access is **Login IP Ranges** on a profile, which is what the steps below configure. ### Before you start Login IP ranges are set on one profile at a time, from that profile's detail page. If you do not see a **Login IP Ranges** section, check that you opened a profile rather than the profile list, and that your edition sets the ranges on profiles rather than on the Session Settings page (see the note at the end of this section). You need the **Manage Profiles and Permission Sets** permission to add or edit login IP ranges, and **View Setup and Configuration** to view them. Two things determine whether this works: * **Whose profile you restrict.** You choose which profile to put the ranges on, and every user on that profile is then restricted by them. It has to be the profile of whoever authorizes the integration, so if that person is on a shared profile such as **System Administrator**, everyone else on that profile is restricted too. Create a user for the integration and give it a profile of its own. * **Which addresses you allow.** The list must include Ampersand's addresses **and** the addresses that same user signs in from in a browser. Authorization happens in a browser, so if their own address is missing they cannot log in to connect or reconnect the integration. ### Step 1: Add the allowed IP ranges to the profile 1. Log in to **Salesforce**. 2. Go to **Setup**. 3. In the **Quick Find** box, search for **Profiles** and open the profile list. 4. Click the **name** of the profile belonging to the user who authorizes the integration. Do not click **Edit** next to it; login IP ranges are not on the edit page, only on the profile's detail page. 5. Find **Login IP Ranges** on that detail page: * In the original profile interface, scroll to the bottom of the page to the **Login IP Ranges** related list and click **New**. * If your org has the enhanced profile user interface enabled, click **Login IP Ranges** in the list of links near the top of the page, then click **Add IP Ranges**. 6. Add one entry for each address listed in [Ampersand's outgoing IP addresses](/security/outgoing-ip-addresses). A single address is entered as a range, so put the same address in both **Start IP Address** and **End IP Address**, and give each entry a description such as `Ampersand` so it is recognizable later. 7. Add one more entry for the range your own users sign in from, so the person who authorizes the integration is not locked out. 8. Click **Save**. At this point, the user cannot log in from anywhere outside the addresses you listed. ### Step 2: Verify the integration still works 1. Reconnect the integration from your vendor's app. 2. Confirm that data still syncs in both directions. If requests start failing with login or authentication errors after this change, an address is missing from the profile's login IP ranges. Restricting login IP addresses is available in all Salesforce editions, but where you set the ranges depends on the edition. In Enterprise, Performance, Unlimited, and Developer editions they are set on a profile, as described above. In other editions they are set from **Setup** → **Session Settings** instead. For details, see Salesforce's guide to [restricting login IP addresses in profiles](https://help.salesforce.com/s/articleView?id=platform.login_ip_ranges.htm\&type=5). ## Subscribe Action permissions If the integration includes Subscribe Actions, enable these permissions. Explicitly enable: * **API Enabled** - allows the integration to make API calls * **Modify Metadata Through Metadata API Functions** — configure event channels and memberships through Metadata API * **Customize Application** — configure artifacts like Named Credentials so Ampersand can connect to event channels securely Salesforce auto-enables these dependent permissions when you enable the ones above: * **View Setup and Configuration** — access setup configuration to create webhook subscription settings * **View Roles and Role Hierarchy** — correct visibility context so Salesforce can deliver events (without this, events may be generated but not delivered) * **Manage Custom Permissions** — create and manage dedicated event channels and channel memberships for the installation Enable these on the profile (Option A, step 1) or permission set (Option B, step 4) you configured above. ## Custom field management permissions If the integration creates or updates custom fields in your Salesforce instance, you will need to enable the following permissions: * **API Enabled** - allows the integration to make API calls * **Modify Metadata Through Metadata API Functions** — allows the integration to call the Metadata API in order to modify custom fields * **Customize Application** — allows the creation and update of custom fields * **Manage Profiles and Permission Sets** - allows a new permission set to be created to give the current user permission to read from and write to the field * **Assign Permission Sets** - allows the integration to assign the new permission set to the current user Enable these on the profile (Option A, step 1) or permission set (Option B, step 4) you configured above. # Salesforce (JWT authentication) Source: https://docs.ampersand.ai/customer-guides/salesforce-jwt Connect Salesforce using the OAuth 2.0 JWT Bearer Flow: generate an RSA key pair, create an External Client App, set OAuth policies, and fix auth errors. This guide walks you through connecting your Salesforce org to an integration that uses the [OAuth 2.0 JWT Bearer Flow](https://help.salesforce.com/s/articleView?id=sf.remoteaccess_oauth_jwt_flow.htm\&language=en_US\&type=5). This is a headless, server-to-server authentication method — there is no interactive login, and the integration runs on behalf of a designated user using credentials you configure up front. If your integration uses the standard Salesforce OAuth flow instead, please refer to the [Salesforce customer guide](/customer-guides/salesforce). If you are unsure which guide is applicable to you, please ask the integration developer. ## Before installing a Salesforce (JWT) integration You will need **Administrator access** to your Salesforce org. ### 1. Generate a certificate and RSA private key The JWT Bearer flow requires an RSA key pair. You'll upload the public certificate to your Salesforce External Client App and share the private key (base64-encoded) with the integration. Open a terminal in the folder where you want to create the files. You need **OpenSSL** available in that environment (macOS and most Linux distributions include it by default). On **Windows**, use **Git Bash** (bundled with [Git for Windows](https://gitforwindows.org/), which includes OpenSSL), **WSL**, or install OpenSSL and ensure it is on your `PATH`. Run the following command to generate a 2048-bit RSA private key and a matching self-signed X.509 certificate in a single step: ```bash theme={"dark"} openssl req -x509 -sha256 -nodes -days 3650 -newkey rsa:2048 \ -keyout private-key.pem -out cert.pem \ -subj "/CN=salesforce-jwt-integration" ``` This produces two files: * `private-key.pem` — Your RSA private key. You will base64-encode this and share it when installing the integration. * `cert.pem` — Your public certificate. You will upload this to Salesforce in a later step. Salesforce requires an RSA key. ECDSA (`ecparam`) keys are not accepted for the JWT Bearer flow. Use a key length of at least 2048 bits. You will then need to base64-encode your private key before sharing it during the installation process. Use the command that matches your environment: **macOS (Terminal)** ```bash theme={"dark"} base64 -i private-key.pem ``` **Linux, Git Bash, or WSL** ```bash theme={"dark"} base64 -w 0 private-key.pem ``` **Windows (PowerShell)** From the directory that contains `private-key.pem`: ```powershell theme={"dark"} [Convert]::ToBase64String([IO.File]::ReadAllBytes((Resolve-Path 'private-key.pem'))) ``` Copy the encoded string (a long single line, e.g. starting with `LS0tLS1CRUdJTiBSU0EgUFJJVkFURSBLRVktLS0tL...`). It will be required during the installation process. ### 2. Create the External Client App The External Client App identifies the integration to Salesforce and holds the certificate used to verify JWT signatures. 1. Log in to *Salesforce* as an Administrator. 2. Navigate to the gear icon in the top right corner and select **Setup**. Alt text 3. Search for **External Client App Manager**. 4. Click **New External Client App**. New ECA 5. Fill in the basic information: * **External Client App Name**: e.g. `My Integration` * **API Name**: auto-filled from the name * **Contact Email**: your administrator email * **Distribution State**: **Local** 6. Under **API (Enable OAuth Settings)**: * Check **Enable OAuth**. * For **Callback URL**, enter `https://api.withampersand.com/callbacks/v1/oauth`. The JWT Bearer flow does not use the callback, but Salesforce requires a value. * Under **Selected OAuth Scopes**, add: * **Manage user data via APIs (`api`)** * **Perform requests at any time (`refresh_token`, `offline_access`)** — this is required for Salesforce to accept JWT Bearer exchanges even though no refresh token is issued. 7. Under **Flow Enablement**, check **Enable JWT Bearer Flow** and upload the `cert.pem` file you generated in the previous step. 8. Under **Security**, ensure **Require Proof Key for Code Exchange (PKCE) extension for Supported Authorization Flows** is unchecked. (It's unchecked by default, but worth verifying.) Leave **Issue JSON Web Token (JWT)-based access tokens for named users** unchecked. Despite the similar name, this is a different feature that changes the format of access tokens Salesforce returns — and those JWT-format tokens only work with the REST API, not with the Bulk API or Metadata API that this integration also uses. Enabling it will break bulk writes and subscribe-action registration. 9\. Click **Create**. After saving, it may take 2 to 10 minutes for Salesforce to propagate the new External Client App. If you hit errors when testing the integration immediately after creation, wait a few minutes and try again. ### 3. Copy the Consumer Key 1. In **Setup**, open **External Client App Manager** again. 2. Select your External Client App from the list. 3. Go to the **Settings** tab and expand **OAuth Settings**. 4. Under **App Settings**, click **Consumer Key and Secret**. This opens a new page — you may be prompted to verify your identity before the values are shown. 5. Copy the **Consumer Key**. Store it securely — you will need it during the installation step. (The Consumer Secret is not needed for JWT Bearer flow.) ### 4. Configure OAuth policies 1. In **Setup**, open **External Client App Manager** and select your External Client App. 2. Go to the **Policies** tab, click **Edit**. 3. Expand **OAuth Policies**. 4. Scroll down to **App Authorization** section: * Set **Refresh Token Policy** to **Refresh token is valid until revoked**. * Set **IP Relaxation** to **Relax IP restrictions**. 5. Click **Save**. Setting **Refresh Token Policy** to **Refresh token is valid until revoked** is also required, even though the JWT Bearer flow never actually issues a refresh token. Salesforce internally checks for a non-expired approval record for the user before issuing an access token, and any other setting causes that approval to age out — which silently breaks the integration later. The other options (`Immediately expire`, `Expire after specific time`, etc.) will cause `invalid_grant` errors. ### 5. Create an integration user The JWT Bearer flow runs as a specific Salesforce user. Every API call made by the integration is attributed to this user, and its Profile and permission sets determine what data the integration can read and write. #### 1. Create a new user 1. Click the gear icon in the top-right corner and select **Setup**. 2. In the left-hand search bar, type **Users** and open it. 3. Create a new user: * For **User License**, select `Salesforce Integration`. * For **Profile**, select `Minimum Access - API Only Integrations`. #### 2. Create a Permission Set 1. Click the gear icon in the top-right corner and select **Setup**. 2. In the left-hand search bar, type **Permission Sets** and open it. 3. Click on "New" to create a new permission set. 4. Follow the prompts to create the permission set: * For the name, you can call it something general like `Integration User Permission Set` or something that describes the level of access it has - such as `Account and Contact Access`. * In the **License** dropdown, you must select `Salesforce API Integration`. #### 3. Configure object permissions 1. Click on **Object Settings** 2. For each of the objects that the integration needs to access you need to set up its permissions. Start by clicking on the first object, for example "Accounts". 3. Edit the permission so that all relevant boxes under **Object Permissions** and **Field Permissions** are checked. Then click on **Save**. 4. Repeat for all the other objects that the integration needs to access. #### 4. Configure system permissions 1. Select **System Permissions**. 2. Ensure that the checkboxes for the necessary [system permissions](/customer-guides/salesforce#required-system-permissions) are selected, and then click **Save**. #### 5. Assign permission set to integration user 1. Click on **Manage Assignments**. 2. Click on **Add Assignment**. 3. Select the integration user you created in step 1. 4. Ensure "Expires On" is set to "Never Expires", and then click on "Assign". The username you chose when creating the user is exactly the value you'll paste into the **Salesforce Username** field when installing the integration. Copy it somewhere you can find it later. ### 6. Associate the Permission Set with the app You need to add the permission set you created in step 5 to the app's pre-authorized Permission Sets list. This is what satisfies the **Admin approved users are pre-authorized** OAuth policy from step 4 — it tells Salesforce "any user who has this permission set is allowed to use this app." 1. In **Setup**, open **External Client App Manager** and select your External Client App. 2. Go to the **Policies** tab, click **Edit**. 3. Expand **OAuth Policies**, under **Plugin Policies**, set **Permitted Users** to **Admin approved users are pre-authorized**. Click on **OK** in the dialog that pops up. 4. Expand **App Policies**, under **Select Permission Sets**, move the permission set that you created in step 5 from **Available Permission Sets** to **Selected Permission Sets** using the arrow button (`>`). 5. Click **Save**. This is the step that actually pre-authorizes the integration user for JWT Bearer. Without it — even if everything else is configured correctly — Salesforce rejects the JWT assertion with `invalid_app_access: user is not admin approved to access this app`. ### 7. Confirm your Salesforce subdomain Your Salesforce My Domain subdomain is the unique part of your Salesforce URL. You will need it to install the integration. * **Production**: if your login URL is `https://acme.my.salesforce.com`, your subdomain is `acme`. * **Sandbox (Enhanced Domains)**: if your login URL is `https://acme--dev.sandbox.my.salesforce.com`, your subdomain is `acme--dev.sandbox`. * **Sandbox (legacy)**: if your login URL is `https://acme--dev.my.salesforce.com`, your subdomain is `acme--dev`. * **Developer accounts**: if your login URL is `https://acme-dev-ed.develop.my.salesforce.com`, your subdomain is `acme-dev-ed.develop`. You can also find your subdomain in **Setup** > **My Domain**. ## Installing the integration When you are installing the integration, you will be prompted for a number of inputs. Here is where to find each value: | Input | Where to find it | | - | - | | **Consumer Key** | From step 3. | | **Salesforce Username** | The username of the integration user from step 5. | | **Private Key** | Base64-encoded contents of `private-key.pem` (from step 1). | | **Subdomain** | Your My Domain subdomain (from step 7). | **Sandbox refresh**: After refreshing a sandbox, the integration user's username may be re-suffixed with the sandbox name (e.g. `integration@acme.com` becomes `integration@acme.com.dev`). If this happens, update the **Salesforce Username** field on your Ampersand connection to match the new username. ## Troubleshooting If the integration experiences authentication errors after setup, here are some common causes. * **`invalid_app_access: user is not admin approved to access this app`**: The integration user's permission set is not in the app's **Selected Permission Sets** list. Revisit step 6 and confirm the permission set from step 5 is in **Selected Permission Sets** (not just **Available Permission Sets**), and that the permission set is actually assigned to the integration user. To reproduce the error independently, use the Salesforce CLI: `sf org login jwt --client-id --jwt-key-file private-key.pem --username --instance-url https://.my.salesforce.com`. * **`user hasn't approved this consumer`**: Either the integration user's permission set is not associated with your app (revisit step 6), or the **Refresh Token Policy** on the app is not set to **Refresh token is valid until revoked** (revisit step 4). Both are required. * **Worked for weeks or months, then started failing with `invalid_grant`**: Almost always the Refresh Token Policy silently aging out the approval. Set **Refresh Token Policy** to **Refresh token is valid until revoked** and re-associate the permission set if needed. * **`invalid_grant: audience is invalid`**: The production vs sandbox environment was auto-detected incorrectly from your subdomain. Double-check that your subdomain string matches the format shown in step 7 — sandboxes should include a `--` or `.sandbox` segment. * **`invalid_grant: invalid assertion`**: The private key or certificate is mismatched. Regenerate the pair in step 1 and re-upload the certificate in step 2. * **`not authorized`**: Check **IP Relaxation** is set to **Relax IP restrictions** (step 4), and that the integration user's permission set has **API Enabled** under **System Permissions**. * **Works immediately after setup, then fails**: Wait 2 to 10 minutes after creating or modifying the app for Salesforce to propagate the changes. # Snowflake Source: https://docs.ampersand.ai/customer-guides/snowflake The Snowflake provider is currently in private preview and not yet generally available. Learn how to request access by emailing support@withampersand.com. This provider is currently in private preview. Please reach out to us at [support@withampersand.com](mailto:support@withampersand.com) to get access. # Update a connection Source: https://docs.ampersand.ai/customer-guides/update-connection Update the credentials for an integration from the Manage tab, including when to re-authenticate, such as a bad_credentials status or switching accounts. If the credentials you provided for an integration need to be updated, you can use the **Manage** tab in the UI component to update your connection. ## When to update a connection Re-authentication for the connection may be required in the following scenarios: * In the **Manage** tab, the **Status** of the connection is showing as "bad\_credentials". * Your integration isn't working. * You need to switch to a different account. ## How to update a connection 1. Navigate to the **Manage** tab. Permitted Users 2. Under the **Update connection** section enter credentials as needed and click the **Next** button. ## If Manage tab isn't visible If you do not see the **Manage** tab, it means that the app builder needs to update `@amp-labs/react` to v2.7+ in their code. # Zoho Source: https://docs.ampersand.ai/customer-guides/zoho Prepare your Zoho account by activating Zoho CRM with an organization user, and create Zoho Mail outgoing webhooks for Subscribe Actions. ## Checklist before installing a Zoho integration Before you proceed with the Zoho integration, you need to have a Zoho account with the Zoho CRM application activated. If you haven't already done so, follow these steps to activate Zoho CRM on your existing Zoho account. ### Activate Zoho CRM > **Note:** Having a general Zoho account (e.g., for Zoho Mail or Writer) does not automatically grant you access to Zoho CRM. You must sign up for it explicitly. If you have not yet activated Zoho CRM, follow these steps: 1. Navigate to the [Zoho CRM signup page](https://www.zoho.com/crm/signup.html). 2. Enter your **Full Name**. 3. Enter your **Work Email**. If this email is already associated with a Zoho account, you will be prompted to sign in. 4. Enter your **Password**. 5. Provide your phone number. 6. Click **GET STARTED**. Activating ZohoCRM The integration supports all available Zoho CRM editions: Free, Standard, Professional, Enterprise, Ultimate and CRM Plus. Although some actions like creating custom fields are not supported with the free plan. ### User Permissions In Zoho CRM, there are two main types of users: * Organization users * Team users You should use an **organization user** account when connecting to the integration. ## Zoho Mail: set up webhooks for real-time updates (Subscribe Actions) If your integration uses Zoho Mail Subscribe Actions, you need to create an **outgoing webhook** in the Zoho Mail console. Zoho Mail has no API for this, so it must be configured by hand. ### Checklist before you start * You have a Zoho Mail account with access to the **Developer Space** section of **Settings**. * Your developer has provided you with a **webhook URL** in this format: ``` https://subscribe-receiver.withampersand.com/zt1secx7jknlhb/v1/projects/PROJECT_ID/integrations/INTEGRATION_ID/installations/INSTALLATION_ID ``` ### Create the outgoing webhook 1. Sign in to [Zoho Mail](https://mail.zoho.com) and open **Settings**. 2. Scroll to the **Developer Space** section in the left sidebar and select **Webhooks**. 3. Select the **Outgoing Webhooks** tab and click **Create new configuration**. Make sure you are on the **Outgoing Webhooks** tab — that is what sends events from Zoho Mail to your developer's application. Outgoing Webhooks in Zoho Mail settings 4. Select the entity you want to send events for: * **Mail** — fires a webhook each time a new email is received. You can optionally narrow it down with conditions (e.g. sender or subject filters) or tags. * **Tasks** — fires a webhook on task activity in groups you own or moderate (you can select up to 3 groups). 5. Give the webhook a name and paste the webhook URL your developer gave you into the **Webhook URL** field. 6. Save the webhook. If your integration subscribes to both new emails and task activity, create a webhook for each entity, pointing at the same URL. For official setup details, see the [Zoho Mail outgoing webhook documentation](https://www.zoho.com/mail/help/dev-platform/webhook.html). # Azure Service Bus destinations Source: https://docs.ampersand.ai/destinations/azureservicebus Publish Read and Subscribe Action payloads as JSON messages to an Azure Service Bus queue or topic using a connection string with Send permission. For more information on destinations, see the [Destinations](/destinations) page. When new data is read from a SaaS instance via a [Read Action](/read-actions) or [Subscribe Action](/subscribe-actions), Ampersand publishes the payload as messages to your Azure Service Bus queue or topic. ## Prerequisites Before setting up an Azure Service Bus destination, ensure that you have: * An Azure Service Bus namespace with a queue or topic * A connection string with `Send` permission on that queue or topic ## Data format Ampersand publishes each read or subscribe message to your queue or topic as JSON. The full payload schema is defined in [webhook.yaml](https://github.com/amp-labs/openapi/blob/main/webhook/webhook.yaml). ## Create an Azure Service Bus destination Go to the [Destinations page](https://dashboard.withampersand.com/projects/_/destinations) in the Ampersand Dashboard and create a new Azure Service Bus destination. You'll need to provide: | Field | Type | Required | Example | Description | | - | - | - | - | - | | **Destination name** | string | Yes | `ampersandServiceBus` | Alias to reference in your `amp.yaml` file | | **Name** | string | Yes | `ampersand-events` | Name of the queue or topic to publish to | | **Connection string** | string | Yes | | Connection string with `Send` permission | ## Refer to the destination in your integration After creating your Azure Service Bus destination, reference it in your `amp.yaml` file: ```yaml theme={"dark"} specVersion: 1.0.0 integrations: - name: salesforceToServiceBus displayName: Salesforce to Azure Service Bus provider: salesforce read: objects: - objectName: account destination: ampersandServiceBus - objectName: contact destination: ampersandServiceBus ``` # BigQuery destinations Source: https://docs.ampersand.ai/destinations/bigquery Insert Read and Subscribe Action data into a Google BigQuery table with id and payload columns, using a service account key and optional batch settings. For more information on destinations, see the [Destinations](/destinations) page. When new data is read from a SaaS instance via a [Read Action](/read-actions) or [Subscribe Action](/subscribe-actions), Ampersand inserts the payload as rows into your Google BigQuery table. ## Prerequisites Before setting up a BigQuery destination, ensure that you have: * A Google Cloud project with BigQuery enabled * A dataset and table to insert into (see [Data format and table setup](#data-format-and-table-setup)) * A service account with the `BigQuery Data Editor` role (or `bigquery.tables.updateData` permission) on that dataset, and a JSON key for it ## Data format and table setup Ampersand delivers each read or subscribe message as a JSON object. The full payload schema is defined in [webhook.yaml](https://github.com/amp-labs/openapi/blob/main/webhook/webhook.yaml). The BigQuery destination writes each message into two columns: `id` (a generated identifier) and `payload` (the full message JSON, written as a string). Create the table with exactly these columns: ```sql theme={"dark"} CREATE TABLE `my-project.my_dataset.my_table` ( id STRING, payload STRING ); ``` ## Create a BigQuery destination Go to the [Destinations page](https://dashboard.withampersand.com/projects/_/destinations) in the Ampersand Dashboard and create a new BigQuery destination. You'll need to provide: | Field | Type | Required | Example | Description | | - | - | - | - | - | | **Destination name** | string | Yes | `ampersandBigQuery` | Alias to reference in your `amp.yaml` file | | **Project ID** | string | Yes | `my-gcp-project` | Google Cloud project ID | | **Dataset ID** | string | Yes | `analytics` | BigQuery dataset ID | | **Table ID** | string | Yes | `ampersand_events` | BigQuery table ID | | **Service account credentials** | string | Yes | | Service account key JSON with write access to the table | | **Batch size** | integer | No | `1000` | Rows to buffer before flushing a batch (defaults to 1000) | | **Max wait (seconds)** | integer | No | `30` | Max seconds to wait before flushing a batch (defaults to 30) | ## Refer to the destination in your integration After creating your BigQuery destination, reference it in your `amp.yaml` file: ```yaml theme={"dark"} specVersion: 1.0.0 integrations: - name: salesforceToBigQuery displayName: Salesforce to BigQuery provider: salesforce read: objects: - objectName: account destination: ampersandBigQuery - objectName: contact destination: ampersandBigQuery ``` # ClickHouse destinations Source: https://docs.ampersand.ai/destinations/clickhouse Insert synced data into a ClickHouse table over HTTPS, matching payload keys to column names, and configure the URL, credentials, database, and batch size. For more information on destinations, see the [Destinations](/destinations) page. When new data is read from a SaaS instance via a [Read Action](/read-actions) or [Subscribe Action](/subscribe-actions), Ampersand inserts the payload as rows into your ClickHouse table. ## Prerequisites Before setting up a ClickHouse destination, ensure that you have: * A ClickHouse instance reachable over HTTPS (ClickHouse Cloud or self-hosted) * A table to insert into (see [Data format and table setup](#data-format-and-table-setup)) * A username and password with `INSERT` privileges on that table ## Data format and table setup Ampersand delivers each read or subscribe message as a JSON object. The full payload schema is defined in [webhook.yaml](https://github.com/amp-labs/openapi/blob/main/webhook/webhook.yaml). ClickHouse matches the payload's top-level keys to your table's columns by name. Create a column for each top-level key you want to store. Keys with no matching column are ignored. `result` is the array of synced records; store it as a JSON string. ```sql theme={"dark"} CREATE TABLE ampersand_events ( action String, objectName String, operationId String, installationId String, operationTime String, result String, ... ) ENGINE = MergeTree() ORDER BY tuple(); ``` ## Create a ClickHouse destination Go to the [Destinations page](https://dashboard.withampersand.com/projects/_/destinations) in the Ampersand Dashboard and create a new ClickHouse destination. You'll need to provide: | Field | Type | Required | Example | Description | | - | - | - | - | - | | **Destination name** | string | Yes | `ampersandClickHouse` | Alias to reference in your `amp.yaml` file | | **URL** | string | Yes | `https://abc.clickhouse.cloud:8443` | HTTPS URL of your ClickHouse instance | | **Username** | string | Yes | `default` | ClickHouse username | | **Password** | string | Yes | | ClickHouse password | | **Table name** | string | Yes | `ampersand_events` | Table to insert rows into | | **Database** | string | Yes | `analytics` | Database containing the table | | **Batch size** | integer | No | `1000` | Rows to buffer before flushing a batch (defaults to 1000) | | **Max wait (seconds)** | integer | No | `30` | Max seconds to wait before flushing a batch (defaults to 30) | ## Refer to the destination in your integration After creating your ClickHouse destination, reference it in your `amp.yaml` file: ```yaml theme={"dark"} specVersion: 1.0.0 integrations: - name: salesforceToClickHouse displayName: Salesforce to ClickHouse provider: salesforce read: objects: - objectName: account destination: ampersandClickHouse - objectName: contact destination: ampersandClickHouse ``` # Kinesis destinations Source: https://docs.ampersand.ai/destinations/kinesis Send Read and Subscribe Action messages to an AWS Kinesis stream: create the stream and IAM credentials, set partition keys, and review the message format. For more information on destinations, see the [Destinations](/destinations) page. When new data is read from a SaaS instance via a [Read Action](/read-actions) or [Subscribe Action](/subscribe-actions), Ampersand can send the payload to your AWS Kinesis stream. ## Prerequisites Before setting up a Kinesis destination, ensure that you have: * An AWS account with access to Kinesis * A Kinesis stream (or permissions to create one) * AWS credentials with the following permissions: * `kinesis:PutRecord` * `kinesis:PutRecords` * `kinesis:DescribeStream` ## Create a Kinesis destination ### Step 1: Set up your Kinesis stream If you don't already have a Kinesis stream, create one in the AWS Console or using the AWS CLI: ```bash theme={"dark"} aws kinesis create-stream \ --stream-name ampersand-integration-stream \ --shard-count 1 \ --region us-west-2 ``` Wait for the stream to become active. You can check by running this command: ```bash theme={"dark"} aws kinesis describe-stream \ --stream-name ampersand-integration-stream \ --region us-west-2 ``` You should see `"StreamStatus": "ACTIVE"` in the response. ### Step 2: Create AWS credentials Create an IAM user or role with permissions to write to your Kinesis stream. You need: * **AWS Access Key ID** * **AWS Secret Access Key** * **AWS Session Token** (optional, for temporary credentials) **Example IAM policy:** ```json theme={"dark"} { "Version": "2012-10-17", "Statement": [ { "Effect": "Allow", "Action": [ "kinesis:PutRecord", "kinesis:PutRecords", "kinesis:DescribeStream" ], "Resource": "arn:aws:kinesis:us-west-2:123456789012:stream/ampersand-integration-stream" } ] } ``` ### Step 3: Add the destination to Ampersand Go to the [Destinations page](https://dashboard.withampersand.com/projects/_/destinations) in the Ampersand Dashboard and create a new Kinesis destination. You'll need to provide: | Field | Type | Required | Description | | - | - | - | - | | **Destination name** | string | Yes | Alias to reference in your `amp.yaml` file | | **Stream name** | string | Yes | Name of your Kinesis stream | | **Region** | string | Yes | AWS region where your stream is located (e.g., `us-west-2`) | | **AWS Access Key ID** | string | Yes | AWS access key with Kinesis permissions | | **AWS Secret Access Key** | string | Yes | AWS secret access key | | **AWS Session Token** | string | No | Session token for temporary credentials | | **Partition key template** | string | No | [JMESPath](https://jmespath.org) template for partition key | | **Endpoint URL** | string | No | Custom Kinesis endpoint if applicable (e.g. when using [LocalStack](https://www.localstack.cloud)) | ## Refer to the destination in your integration After creating your Kinesis destination, reference it in your `amp.yaml` file: ```yaml theme={"dark"} specVersion: 1.0.0 integrations: - name: salesforceToKinesis displayName: Salesforce to Kinesis provider: salesforce read: objects: - objectName: account destination: ampersandKinesisStream - objectName: contact destination: ampersandKinesisStream ``` ## Message format Ampersand sends messages to your Kinesis stream in JSON format. All data is wrapped in a top-level `data` object. ### Read action messages ```json theme={"dark"} { "data": { "action": "read", "projectId": "9482e676-4874-43a4-beea-fa8081d13a07", "provider": "hubspot", "groupRef": "group-id-1", "groupName": "group-name-1", "consumerRef": "user-id", "consumerName": "user-name", "installationId": "085353b1-07f1-4209-b471-7e028c0a68c8", "installationUpdateTime": "2025-10-14T02:58:55.595861Z", "objectName": "contacts", "operationId": "0199e0a8-149d-7ee9-9f50-0514403517f9", "operationTime": "2025-10-14T02:58:59.323405308Z", "result": [ { "fields": { "id": "438", "firstname": "Maria", "lastname": "Johnson", "email": "maria@example.com", "city": "Brisbane" }, "raw": { "archived": false, "createdAt": "2023-10-26T17:55:48.301Z", "id": "438", "properties": { "firstname": "Maria", "lastname": "Johnson", "email": "maria@example.com", "city": "Brisbane", "createdate": "2023-10-26T17:55:48.301Z" } } } ] } } ``` **Key fields:** * `action`: The action type (`read` or `subscribe`) * `projectId`: Your Ampersand project identifier * `provider`: The SaaS provider name (e.g., `hubspot`, `salesforce`) * `groupRef` / `groupName`: Customer group identifier and name * `consumerRef` / `consumerName`: End user identifier and name * `installationId`: The installation instance ID * `installationUpdateTime`: When the installation was last updated * `objectName`: The object being synced * `operationId`: Unique identifier for this sync operation * `operationTime`: When the operation completed * `result`: Array of records synced * `fields`: Normalized field data * `raw`: Original API response from the provider ### Subscribe action messages Subscribe action messages follow the same structure as read actions, but include additional event-related fields within each result entry: ```json theme={"dark"} { "data": { "action": "subscribe", "projectId": "9482e676-4874-43a4-beea-fa8081d13a07", "provider": "salesforce", "groupRef": "webhook-demo-group-id", "groupName": "webhook-demo-group-name", "consumerRef": "user-id", "consumerName": "user-name", "installationId": "0c9230e1c-8fbe-4b28-bf10-2beee8fbf4ce", "installationUpdateTime": "2025-04-10T23:00:52.618406Z", "objectName": "company", "operationTime": "2025-04-10T23:22:25.000Z", "result": [ { "fields": { "id": "001Dp00000ZDgmxIAD", "annualrevenue": null, "website": null }, "subscribeEventType": "update", "providerEventType": "UPDATE", "raw": { "Id": "001Dp00000ZDgmxIAD", "AnnualRevenue": null, "Description": "Notes about the account", "Name": "Acme Corp", "Website": null, "attributes": { "type": "Account", "url": "/services/data/v59.0/sobjects/Account/001Dp00000ZDgmxIAD" } }, "rawEvent": { "ChangeEventHeader": { "changeOrigin": "com/salesforce/api/soap/63.0;client=SfdcInternalAPI/", "changeType": "UPDATE", "changedFields": ["Description", "LastModifiedDate"], "commitNumber": 12041091891110, "commitTimestamp": 1744327345000, "commitUser": "005Dp000003Cd1SIAS", "entityName": "Account", "recordId": "001Dp00000ZDgmxIAD", "sequenceNumber": 1, "transactionKey": "00051705-2e99-d1e5-7e7a-48e3af682d07" }, "Description": "Notes about the account", "LastModifiedDate": "2025-04-10T23:22:25.000Z" } } ] } } ``` **Additional fields in subscribe actions:** * `subscribeEventType`: Normalized event type (`create`, `update`, `delete`, `associationUpdate`) * `providerEventType`: Raw event type from the provider API * `rawEvent`: Original webhook event from the provider (when available) ## Partition key configuration Kinesis uses partition keys to distribute messages across shards. (Learn more in [the AWS docs](https://docs.aws.amazon.com/streams/latest/dev/key-concepts.html)). You can customize the partition key using a JMESPath template. JMESPath is a query language for JSON. See [jmespath.org](https://jmespath.org) for the full specification. You can specify a partition key template when creating a Kinesis destination in the [Ampersand Dashboard](https://dashboard.withampersand.com/projects/_/destinations). If you don't specify one, by default we use the message ID as the partition key, so the messages we send will be evenly distributed across your shards. ### Custom partition key examples You can customize the partition key by using any of the attributes inside of the message. Since all message data is wrapped in a `data` object, you need to prefix your JMESPath expressions with `data.`. Here are some examples: **Use installation ID:** ``` data.installationId ``` **Use group reference:** ``` data.groupRef ``` **Use object name:** ``` data.objectName ``` **Combine multiple fields:** ``` join('_', [data.groupRef, data.objectName]) ``` **Use operation ID:** ``` data.operationId ``` ## Troubleshooting ### Messages not appearing in Kinesis **Check destination configuration:** 1. Verify that the stream name and region are correct in the [Ampersand Dashboard](https://dashboard.withampersand.com/projects/_/destinations). 2. Test your AWS credentials: ```bash theme={"dark"} aws kinesis describe-stream \ --stream-name your-stream-name \ --region your-region ``` 3. Check that IAM permissions for the credential include `kinesis:PutRecord` and `kinesis:PutRecords`. ## Limitations * **Message size**: Individual records cannot exceed 1 MB (AWS Kinesis limit). If a single record is bigger than this limit, we send it to you in a download URL, similar to what we do for webhook destinations. ([More details here](/destinations/webhooks#read-action-webhooks)). * **Throughput**: Limited by Kinesis shard capacity (1 MB/sec write per shard). * **Retention**: Messages are retained for 24 hours to 365 days (configurable in AWS). * **Ordering**: Only guaranteed within a single partition key. If you hit these limits, contact `support@withampersand.com` to discuss scaling strategies. # Route synced data and notifications to destinations Source: https://docs.ampersand.ai/destinations/overview Learn which destinations Ampersand supports, how to add one in the Dashboard, and how to route Read Action, Subscribe Action, and notification data to it. Ampersand supports webhook and Slack destinations, S3, a range of data warehouse destinations (ClickHouse, Snowflake, BigQuery, and Redshift), and a range of stream and queue destinations (Kinesis, Amazon SQS, Google Cloud Pub/Sub, RabbitMQ, and Azure Service Bus). Destinations allow you to route data synced from SaaS instances via [Read Actions](/read-actions) or [Subscribe Actions](/subscribe-actions), and to receive real-time [Notifications](/notifications) about important lifecycle events in your projects. ## Add a destination to the Ampersand Dashboard Go to the [Destinations page](https://dashboard.withampersand.com/projects/_/destinations) in the Ampersand Dashboard to add a new Destination. You'll need to provide: * **Destination name**: this is an alias for the destination that you can then refer to in the `amp.yaml` file. * **URL**: this is the URL of your webhook destination. It must start with `https`. In the case of webhooks, you can easily create a temporary one using tools like [Hookdeck Console](https://console.hookdeck.com?provider=ampersand). ## Using destinations for data delivery For read and subscribe actions, you can specify destinations for each object. You can either have one destination for each object or route multiple objects to the same destination. Here's what your `amp.yaml` file might look like if you had created 2 destinations in the Ampersand Dashboard, one named `accountWebhook` and one named `contactWebhook`. ```YAML YAML theme={"dark"} specVersion: 1.0.0 integrations: - name: readFromSalesforce displayName: Salesforce read provider: salesforce read: objects: - objectName: account destination: accountWebhook - objectName: contact destination: contactWebhook ``` ## Using destinations for notifications Destinations can also be used to receive [notifications](/notifications) about important lifecycle events in your project, such as when customers install or update integrations, when backfills complete, or when sync issues occur. Notifications use a topic-based routing system where you connect event types to topics, and topics to destinations. See the [Notifications overview](/notifications) for complete setup instructions. ## Supported destinations * [Webhook destinations](/destinations/webhooks) * [Slack destinations](/destinations/slack) ### Blob storage * [Amazon S3 destinations](/destinations/s3) ### Databases and warehouses * [ClickHouse destinations](/destinations/clickhouse) * [Snowflake destinations](/destinations/snowflake) * [BigQuery destinations](/destinations/bigquery) * [Amazon Redshift destinations](/destinations/redshift) ### Streams and queues * [Azure Service Bus destinations](/destinations/azureservicebus) * [Amazon Kinesis destinations](/destinations/kinesis) * [Google Cloud Pub/Sub destinations](/destinations/pubsub) * [RabbitMQ destinations](/destinations/rabbitmq) * [Amazon SQS destinations](/destinations/sqs) ## Other Destinations We have more destination types on the roadmap, including: * Postgres * Ampersand-hosted Postgres # Google Cloud Pub/Sub destinations Source: https://docs.ampersand.ai/destinations/pubsub Publish Read and Subscribe Action payloads as JSON to a Google Cloud Pub/Sub topic using a project ID, topic ID, and a Pub/Sub Publisher service account. For more information on destinations, see the [Destinations](/destinations) page. When new data is read from a SaaS instance via a [Read Action](/read-actions) or [Subscribe Action](/subscribe-actions), Ampersand publishes the payload as messages to your Google Cloud Pub/Sub topic. ## Prerequisites Before setting up a Pub/Sub destination, ensure that you have: * A Google Cloud project with a Pub/Sub topic * A service account with the `Pub/Sub Publisher` role (or `pubsub.topics.publish` permission) on that topic, and a JSON key for it ## Data format Ampersand publishes each read or subscribe message to your topic as JSON. The full payload schema is defined in [webhook.yaml](https://github.com/amp-labs/openapi/blob/main/webhook/webhook.yaml). ## Create a Pub/Sub destination Go to the [Destinations page](https://dashboard.withampersand.com/projects/_/destinations) in the Ampersand Dashboard and create a new Pub/Sub destination. You'll need to provide: | Field | Type | Required | Example | Description | | - | - | - | - | - | | **Destination name** | string | Yes | `ampersandPubSub` | Alias to reference in your `amp.yaml` file | | **Project ID** | string | Yes | `my-gcp-project` | Google Cloud project ID | | **Topic ID** | string | Yes | `ampersand-events` | Pub/Sub topic ID | | **Service account credentials** | string | Yes | | Service account key JSON with publish access to the topic | ## Refer to the destination in your integration After creating your Pub/Sub destination, reference it in your `amp.yaml` file: ```yaml theme={"dark"} specVersion: 1.0.0 integrations: - name: salesforceToPubSub displayName: Salesforce to Pub/Sub provider: salesforce read: objects: - objectName: account destination: ampersandPubSub - objectName: contact destination: ampersandPubSub ``` # RabbitMQ destinations Source: https://docs.ampersand.ai/destinations/rabbitmq Publish synced data as JSON messages to an existing RabbitMQ exchange, setting the server URL, optional TLS, and publish credentials in the Dashboard. For more information on destinations, see the [Destinations](/destinations) page. When new data is read from a SaaS instance via a [Read Action](/read-actions) or [Subscribe Action](/subscribe-actions), Ampersand publishes the payload as messages to your RabbitMQ instance. ## Prerequisites Before setting up a RabbitMQ destination, ensure that you have: * A RabbitMQ instance reachable from the internet * An exchange that already exists on that instance * A username and password with permission to publish to that exchange ## Data format Ampersand publishes each read or subscribe message to your RabbitMQ exchange as JSON. The full payload schema is defined in [webhook.yaml](https://github.com/amp-labs/openapi/blob/main/webhook/webhook.yaml). ## Create a RabbitMQ destination Go to the [Destinations page](https://dashboard.withampersand.com/projects/_/destinations) in the Ampersand Dashboard and create a new RabbitMQ destination. You'll need to provide: | Field | Type | Required | Example | Description | | - | - | - | - | - | | **Destination name** | string | Yes | `ampersandRabbitMQ` | Alias to reference in your `amp.yaml` file | | **Server URL** | string | Yes | `rabbitmq.example.com:5672` | Host and port of your RabbitMQ instance | | **Exchange** | string | Yes | `ampersand-events` | Name of an exchange that already exists | | **TLS** | boolean | No | `false` | Whether to connect over TLS. Defaults to false (plain AMQP); set to true for managed brokers that require TLS, e.g. CloudAMQP | | **Username** | string | Yes | `ampersand` | Username with permission to publish | | **Password** | string | Yes | | Password for the username | ## Refer to the destination in your integration After creating your RabbitMQ destination, reference it in your `amp.yaml` file: ```yaml theme={"dark"} specVersion: 1.0.0 integrations: - name: salesforceToRabbitMQ displayName: Salesforce to RabbitMQ provider: salesforce read: objects: - objectName: account destination: ampersandRabbitMQ - objectName: contact destination: ampersandRabbitMQ ``` # Amazon Redshift destinations Source: https://docs.ampersand.ai/destinations/redshift Insert synced data into an Amazon Redshift Serverless table via the Redshift Data API, using created_at and payload columns, AWS credentials, and batching. For more information on destinations, see the [Destinations](/destinations) page. When new data is read from a SaaS instance via a [Read Action](/read-actions) or [Subscribe Action](/subscribe-actions), Ampersand inserts the payload as rows into your Amazon Redshift table, delivered to a Redshift Serverless workgroup. ## Prerequisites Before setting up a Redshift destination, ensure that you have: * An Amazon Redshift Serverless workgroup * A target database, and a table to insert into (see [Data format and table setup](#data-format-and-table-setup)) * AWS credentials with permission to run the Redshift Data API and insert into the target table ## Data format and table setup Ampersand delivers each read or subscribe message as a JSON object. The full payload schema is defined in [webhook.yaml](https://github.com/amp-labs/openapi/blob/main/webhook/webhook.yaml). The Redshift destination writes each message into two columns: `created_at` (the insert time) and `payload` (the full message JSON). The payload is written as a string, so `payload` must be a character column large enough to hold it. Create the table like this: ```sql theme={"dark"} CREATE TABLE events ( created_at TIMESTAMP, payload VARCHAR(65535) ); ``` ## Create a Redshift destination Go to the [Destinations page](https://dashboard.withampersand.com/projects/_/destinations) in the Ampersand Dashboard and create a new Redshift destination. You'll need to provide: | Field | Type | Required | Example | Description | | - | - | - | - | - | | **Destination name** | string | Yes | `ampersandRedshift` | Alias to reference in your `amp.yaml` file | | **Region** | string | Yes | `us-west-2` | AWS region where your Redshift resource lives | | **AWS Access Key ID** | string | Yes | `AKIAIOSFODNN7EXAMPLE` | AWS access key with Redshift Data API permissions | | **AWS Secret Access Key** | string | Yes | | AWS secret access key | | **Database** | string | Yes | `dev` | Target database name | | **Schema** | string | Yes | `public` | Target schema | | **Table name** | string | Yes | `events` | Target table | | **Workgroup name** | string | Yes | `my-workgroup` | Redshift Serverless workgroup name | | **Batch size** | integer | No | `1000` | Rows to buffer before flushing a batch (defaults to 1000) | | **Max wait (seconds)** | integer | No | `30` | Max seconds to wait before flushing a batch (defaults to 30) | ## Refer to the destination in your integration After creating your Redshift destination, reference it in your `amp.yaml` file: ```yaml theme={"dark"} specVersion: 1.0.0 integrations: - name: salesforceToRedshift displayName: Salesforce to Redshift provider: salesforce read: objects: - objectName: account destination: ampersandRedshift - objectName: contact destination: ampersandRedshift ``` # Amazon S3 destinations Source: https://docs.ampersand.ai/destinations/s3 Write Read and Subscribe Action payloads as JSON objects to an Amazon S3 bucket, with IAM setup, object key templates, storage class, and troubleshooting. For more information on destinations, see the [Destinations](/destinations) page. When new data is read from a SaaS instance via a [Read Action](/read-actions) or [Subscribe Action](/subscribe-actions), Ampersand can write the payload as objects to your Amazon S3 bucket. ## Prerequisites Before setting up an S3 destination, ensure that you have: * An AWS account with access to S3 * An S3 bucket (or permissions to create one) * AWS credentials with the following permission: * `s3:PutObject` ## Create an S3 destination ### Step 1: Set up your S3 bucket If you don't already have an S3 bucket, create one in the AWS Console or using the AWS CLI: ```bash theme={"dark"} aws s3api create-bucket \ --bucket ampersand-integration-bucket \ --region us-west-2 \ --create-bucket-configuration LocationConstraint=us-west-2 ``` ### Step 2: Create AWS credentials Create an IAM user or role with permissions to write to your S3 bucket. You need: * **AWS Access Key ID** * **AWS Secret Access Key** * **AWS Session Token** (optional, for temporary credentials) **Example IAM policy:** ```json theme={"dark"} { "Version": "2012-10-17", "Statement": [ { "Effect": "Allow", "Action": "s3:PutObject", "Resource": "arn:aws:s3:::ampersand-integration-bucket/*" } ] } ``` ### Step 3: Add the destination to Ampersand Go to the [Destinations page](https://dashboard.withampersand.com/projects/_/destinations) in the Ampersand Dashboard and create a new S3 destination. You'll need to provide: | Field | Type | Required | Description | | - | - | - | - | | **Destination name** | string | Yes | Alias to reference in your `amp.yaml` file | | **Bucket** | string | Yes | Name of your S3 bucket | | **Region** | string | Yes | AWS region where your bucket is located (e.g., `us-west-2`) | | **AWS Access Key ID** | string | Yes | AWS access key with S3 permissions | | **AWS Secret Access Key** | string | Yes | AWS secret access key | | **AWS Session Token** | string | No | Session token for temporary credentials | | **Object key template** | string | No | [JMESPath](https://jmespath.org) template for object key naming | | **Storage class** | string | No | S3 storage class for written objects (defaults to `STANDARD`) | ## Refer to the destination in your integration After creating your S3 destination, reference it in your `amp.yaml` file: ```yaml theme={"dark"} specVersion: 1.0.0 integrations: - name: salesforceToS3 displayName: Salesforce to S3 provider: salesforce read: objects: - objectName: account destination: ampersandS3Bucket - objectName: contact destination: ampersandS3Bucket ``` ## Message format Ampersand writes each message as a JSON object to your S3 bucket. The object body is the message payload itself — there is no wrapper envelope. In addition to the object body, each S3 object carries event metadata as [S3 object metadata](https://docs.aws.amazon.com/AmazonS3/latest/userguide/UsingMetadata.html) (`x-amz-meta-*` headers), including `projectId`, `installationId`, `connectionId`, `destinationId`, `operationId`, `objectName`, `event-id`, `timestamp`, and `topic`. ### Read action messages ```json theme={"dark"} { "action": "read", "projectId": "9482e676-4874-43a4-beea-fa8081d13a07", "provider": "hubspot", "groupRef": "group-id-1", "groupName": "group-name-1", "consumerRef": "user-id", "consumerName": "user-name", "installationId": "085353b1-07f1-4209-b471-7e028c0a68c8", "installationUpdateTime": "2025-10-14T02:58:55.595861Z", "objectName": "contacts", "operationId": "0199e0a8-149d-7ee9-9f50-0514403517f9", "operationTime": "2025-10-14T02:58:59.323405308Z", "result": [ { "fields": { "id": "438", "firstname": "Maria", "lastname": "Johnson", "email": "maria@example.com", "city": "Brisbane" }, "raw": { "archived": false, "createdAt": "2023-10-26T17:55:48.301Z", "id": "438", "properties": { "firstname": "Maria", "lastname": "Johnson", "email": "maria@example.com", "city": "Brisbane", "createdate": "2023-10-26T17:55:48.301Z" } } } ] } ``` **Key fields:** * `action`: The action type (`read` or `subscribe`) * `projectId`: Your Ampersand project identifier * `provider`: The SaaS provider name (e.g., `hubspot`, `salesforce`) * `groupRef` / `groupName`: Customer group identifier and name * `consumerRef` / `consumerName`: End user identifier and name * `installationId`: The installation instance ID * `installationUpdateTime`: When the installation was last updated * `objectName`: The object being synced * `operationId`: Unique identifier for this sync operation * `operationTime`: When the operation completed * `result`: Array of records synced * `fields`: Normalized field data * `raw`: Original API response from the provider ### Subscribe action messages Subscribe action messages follow the same structure as read actions, but include additional event-related fields within each result entry: ```json theme={"dark"} { "action": "subscribe", "projectId": "9482e676-4874-43a4-beea-fa8081d13a07", "provider": "salesforce", "groupRef": "webhook-demo-group-id", "groupName": "webhook-demo-group-name", "consumerRef": "user-id", "consumerName": "user-name", "installationId": "0c9230e1-8fbe-4b28-bf10-2beee8fbf4ce", "installationUpdateTime": "2025-04-10T23:00:52.618406Z", "objectName": "company", "operationTime": "2025-04-10T23:22:25.000Z", "result": [ { "fields": { "id": "001Dp00000ZDgmxIAD", "annualrevenue": null, "website": null }, "subscribeEventType": "update", "providerEventType": "UPDATE", "raw": { "Id": "001Dp00000ZDgmxIAD", "AnnualRevenue": null, "Description": "Notes about the account", "Name": "Acme Corp", "Website": null, "attributes": { "type": "Account", "url": "/services/data/v59.0/sobjects/Account/001Dp00000ZDgmxIAD" } }, "rawEvent": { "ChangeEventHeader": { "changeOrigin": "com/salesforce/api/soap/63.0;client=SfdcInternalAPI/", "changeType": "UPDATE", "changedFields": ["Description", "LastModifiedDate"], "commitNumber": 12041091891110, "commitTimestamp": 1744327345000, "commitUser": "005Dp000003Cd1SIAS", "entityName": "Account", "recordId": "001Dp00000ZDgmxIAD", "sequenceNumber": 1, "transactionKey": "00051705-2e99-d1e5-7e7a-48e3af682d07" }, "Description": "Notes about the account", "LastModifiedDate": "2025-04-10T23:22:25.000Z" } } ] } ``` **Additional fields in subscribe actions:** * `subscribeEventType`: Normalized event type (`create`, `update`, `delete`, `associationUpdate`) * `providerEventType`: Raw event type from the provider API * `rawEvent`: Original webhook event from the provider (when available) ## Object key configuration Each message is written as a separate object in your bucket. You can customize the object key (the object's path within the bucket) using a JMESPath template. JMESPath is a query language for JSON. See [jmespath.org](https://jmespath.org) for the full specification. You can specify an object key template when creating an S3 destination in the [Ampersand Dashboard](https://dashboard.withampersand.com/projects/_/destinations). If you don't specify one, the default key is the message timestamp followed by the message ID: ``` 2025-10-14T02:58:59.323405308Z_0199e0a8-149d-7ee9-9f50-0514403517f9.json ``` ### Template context Your template can reference three namespaces: | Namespace | Fields | | - | - | | `metadata` | `projectId`, `installationId`, `connectionId`, `destinationId`, `operationId`, `objectName`, `event-id`, `timestamp`, `topic` | | `time` | `year`, `month`, `day`, `hour`, `minute`, `second`, `date`, `datetime`, `unix`, `rfc3339`, `rfc3339_nano` | | `data` | The message payload (e.g., `data.installationId` — see [Message format](#message-format) above) | JMESPath is case-sensitive: `metadata.operationId` works, `metadata.operationid` does not. Fields containing a hyphen must be quoted, e.g. `metadata."event-id"`. Templates are validated when you create or update the destination. ### Custom object key examples **Group objects by synced object name:** ``` join('/', [metadata.objectName, metadata.operationId]) ``` **Partition by date:** ``` join('/', [metadata.objectName, time.date, metadata."event-id"]) ``` **Add a file extension:** ``` join('', [time.rfc3339_nano, '_', metadata."event-id", '.json']) ``` **Use a field from the payload:** ``` join('/', [data.installationId, metadata.operationId]) ``` Make sure your template produces a unique key for every message — include `metadata."event-id"` or `metadata.operationId`. If two messages evaluate to the same key, the later object overwrites the earlier one. ## Storage class By default, objects are written with the `STANDARD` storage class. You can choose a different class when creating the destination, such as `STANDARD_IA`, `ONEZONE_IA`, `INTELLIGENT_TIERING`, `GLACIER`, `GLACIER_IR`, or `DEEP_ARCHIVE`. See [the AWS docs](https://docs.aws.amazon.com/AmazonS3/latest/userguide/storage-class-intro.html) for a comparison. ## Troubleshooting ### Objects not appearing in S3 **Check destination configuration:** 1. Verify that the bucket name and region are correct in the [Ampersand Dashboard](https://dashboard.withampersand.com/projects/_/destinations). 2. Test your AWS credentials: ```bash theme={"dark"} echo '{}' > test.json aws s3api put-object \ --bucket your-bucket-name \ --key ampersand-test.json \ --body test.json ``` 3. Check that IAM permissions for the credential include `s3:PutObject` on the bucket. ## Limitations * **Message size**: Unlike Kinesis, S3 destinations have no practical message size limit — large payloads are written directly to your bucket. * **One object per message**: Each message becomes a separate S3 object. High-volume syncs produce many small objects, which affects S3 request costs. * **Ordering**: S3 objects are independent; there are no ordering guarantees. Use the `timestamp` object metadata or a time-based key template to order messages. If you have questions about scaling, contact `support@withampersand.com`. # Slack destinations Source: https://docs.ampersand.ai/destinations/slack Set up a Slack destination with an incoming webhook URL to post notifications and small read or subscribe payloads, and understand message truncation. For more information on destinations, see the [Destinations](/destinations) page. A Slack destination delivers messages to a Slack channel using a [Slack incoming webhook](https://docs.slack.dev/messaging/sending-messages-using-incoming-webhooks). It's a great fit for [Notifications](/notifications) or quickly testing webhook payloads. For example, posting a message to your team's channel whenever a customer connects an integration or a sync runs into an error may be a good fit for this destination. ## Setting up a Slack destination 1. In Slack, [create a Slack app](https://api.slack.com/apps?new_app=1) (or use an existing one), enable **Incoming Webhooks**, and add a webhook to the channel you want Ampersand to post to. Slack gives you a URL that looks like `https://hooks.slack.com/services/T00000000/B00000000/XXXXXXXX`. 2. In the [Ampersand Dashboard](https://dashboard.withampersand.com/projects/_/destinations), add a new destination of type **Slack** and paste in that URL. (The URL must be a `hooks.slack.com` URL.) The Slack channel is fixed when you create the incoming webhook. Ampersand posts to that channel and cannot override it. ## Notifications Notifications are rendered as a short, human-friendly sentence, followed by the raw notification payload for full detail. For example, a `connection.error` notification appears as: > ⚠️ Salesforce connection for Acme (ID `conn_123`) encountered an error: The refresh token is invalid or expired > > ``` > {"notificationType":"connection.error","data":{ ... }} > ``` You can route notification events to a Slack destination the same way as any other destination. See the [Notifications overview](/notifications) for setup. ## Read & subscribe data Data read via [Read Actions](/read-actions) or [Subscribe Actions](/subscribe-actions) is delivered as the JSON payload nested directly into the Slack message text. Slack renders roughly **16-20 KB** per message and **silently truncates** anything larger. The request still succeeds, so oversized content is dropped without an error. Be careful using Slack destinations for data delivery: a large record or batch may be cut off. For anything beyond small payloads, use a [webhook](/destinations/webhooks), [Kinesis](/destinations/kinesis), or [S3](/destinations/s3) destination instead. # Snowflake destinations Source: https://docs.ampersand.ai/destinations/snowflake Send read and subscribe data into a Snowflake table with key-pair authentication, create the id, created_at, and payload columns, and set batching. For more information on destinations, see the [Destinations](/destinations) page. When new data is read from a SaaS instance via a [Read Action](/read-actions) or [Subscribe Action](/subscribe-actions), Ampersand inserts the payload as rows into your Snowflake table. ## Prerequisites Before setting up a Snowflake destination, ensure that you have: * A Snowflake account * A user configured for [key-pair authentication](https://docs.snowflake.com/en/user-guide/key-pair-auth) (Ampersand authenticates with an RSA private key, not a password) * A target database, schema, and table (see [Data format and table setup](#data-format-and-table-setup)), with `INSERT` privileges granted to that user ## Data format and table setup Ampersand delivers each read or subscribe message as a JSON object. The full payload schema is defined in [webhook.yaml](https://github.com/amp-labs/openapi/blob/main/webhook/webhook.yaml). The Snowflake destination writes each message into three columns: `id` (a generated identifier), `created_at` (the insert time), and `payload` (the full message JSON, written as text). Create the table with exactly these columns: ```sql theme={"dark"} CREATE TABLE my_db.my_schema.my_table ( id TEXT, created_at TIMESTAMP, payload TEXT ); ``` ## Create a Snowflake destination Go to the [Destinations page](https://dashboard.withampersand.com/projects/_/destinations) in the Ampersand Dashboard and create a new Snowflake destination. You'll need to provide: | Field | Type | Required | Example | Description | | - | - | - | - | - | | **Destination name** | string | Yes | `ampersandSnowflake` | Alias to reference in your `amp.yaml` file | | **Account identifier** | string | Yes | `myorg-myaccount` | Your Snowflake account identifier | | **User ID** | string | Yes | `SVC_AMPERSAND` | Snowflake user configured for key-pair auth | | **Private key** | string | Yes | | Unencrypted **PKCS#8** PEM-encoded RSA private key for that user (headers `-----BEGIN PRIVATE KEY-----`). Convert a PKCS#1 key with `openssl pkcs8 -topk8 -nocrypt` | | **Database** | string | Yes | `ANALYTICS` | Target database | | **Schema** | string | Yes | `PUBLIC` | Target schema | | **Table name** | string | Yes | `ampersand_events` | Target table | | **Batch size** | integer | No | `1000` | Rows to buffer before flushing a batch (defaults to 1000) | | **Max wait (seconds)** | integer | No | `30` | Max seconds to wait before flushing a batch (defaults to 30) | ## Refer to the destination in your integration After creating your Snowflake destination, reference it in your `amp.yaml` file: ```yaml theme={"dark"} specVersion: 1.0.0 integrations: - name: salesforceToSnowflake displayName: Salesforce to Snowflake provider: salesforce read: objects: - objectName: account destination: ampersandSnowflake - objectName: contact destination: ampersandSnowflake ``` # Amazon SQS destinations Source: https://docs.ampersand.ai/destinations/sqs Publish read and subscribe action data as JSON messages to an Amazon SQS queue using a queue URL and AWS credentials with sqs:SendMessage permission. For more information on destinations, see the [Destinations](/destinations) page. When new data is read from a SaaS instance via a [Read Action](/read-actions) or [Subscribe Action](/subscribe-actions), Ampersand publishes the payload as messages to your Amazon SQS queue. ## Prerequisites Before setting up an SQS destination, ensure that you have: * An AWS account with an SQS queue * AWS credentials with `sqs:SendMessage` permission on that queue ## Data format Ampersand publishes each read or subscribe message to your queue as JSON. The full payload schema is defined in [webhook.yaml](https://github.com/amp-labs/openapi/blob/main/webhook/webhook.yaml). ## Create an SQS destination Go to the [Destinations page](https://dashboard.withampersand.com/projects/_/destinations) in the Ampersand Dashboard and create a new SQS destination. You'll need to provide: | Field | Type | Required | Example | Description | | - | - | - | - | - | | **Destination name** | string | Yes | `ampersandSQS` | Alias to reference in your `amp.yaml` file | | **Queue URL** | string | Yes | `https://sqs.us-east-1.amazonaws.com/123456789012/my-queue` | URL of your SQS queue | | **AWS Access Key ID** | string | Yes | `AKIAIOSFODNN7EXAMPLE` | AWS access key with `sqs:SendMessage` permission | | **AWS Secret Access Key** | string | Yes | | AWS secret access key | ## Refer to the destination in your integration After creating your SQS destination, reference it in your `amp.yaml` file: ```yaml theme={"dark"} specVersion: 1.0.0 integrations: - name: salesforceToSQS displayName: Salesforce to SQS provider: salesforce read: objects: - objectName: account destination: ampersandSQS - objectName: contact destination: ampersandSQS ``` # Webhook destinations Source: https://docs.ampersand.ai/destinations/webhooks Receive read and subscribe action results at your webhook endpoint, handle inline or URL payloads and size limits, and verify Svix signatures. For more information on destinations, see the [Destinations](/destinations) page. When new data is read from a SaaS instance via a [Read Action](/read-actions) or [Subscribe Action](/subscribe-actions), Ampersand sends a payload to your configured webhook. ## OpenAPI spec Check out our [OpenAPI spec](https://github.com/amp-labs/openapi/blob/main/webhook/webhook.yaml) for the full webhook message schema and more details on the data structure. You can use this to generate types for your language and easily parse the webhook message. ## Read action webhooks For read actions, Ampersand attaches results to a webhook message in two ways: 1. **Inline**: In most cases, the result is directly included in the webhook's `result` field. If a read action returns a large number of records, you might receive multiple webhooks in quick succession, each delivering a subset of the data. Read more about [message frequency](#handling-the-message-frequency) here. 2. **URL**: If a single record in the result set is over 300KB, it becomes too big to be sent over a webhook. In this case, the result won't be included inline. Instead, the webhook message will contain a signed download URL in `resultInfo.downloadUrl`. **The URL expires after 15 minutes.** `resultInfo.type` indicates how the result is attached to the message: If type is `inline`, the full result is in the `result` field. If type is `url`, you can fetch the full result by making a GET request to `resultInfo.downloadUrl`. In both cases, `resultInfo.numRecords` tells you how many records are available in the result. ### Example webhook message (inline) Here's a sample webhook message for a Salesforce Contact object: ```JavaScript JavaScript theme={"dark"} { "action": "read", "consumerRef": "user-123", "consumerName": "John Doe", "groupRef": "xyz-company", "groupName": "XYZ Company", "installationId": "installation-id", "installationUpdateTime": "2024-12-07T00:20:36Z", "objectName": "Contact", "operationId": "operation-uuid", "operationTime": "2024-12-07T00:20:40Z", "projectId": "ampersand-project-id", "provider": "salesforce", // If there is a mapping for this object, rawObjectName contains the original provider API name. "rawObjectName": "Contact", // readType indicates the type of read operation: // - "backfill": Initial backfill. // - "scheduled": Regular scheduled read. // - "triggered": On-demand read triggered via API. "readType": "scheduled", // Only certain providers will have workspace available. "workspace": "salesforce-subdomain", "resultInfo": { "type": "inline", "numRecords": 2 }, "result": [ { "fields": { "id": "001Dp00000P8QurIAF", "firstname": "Sally", "lastname": "Jones" }, "mappedFields": { "pronoun": "she" }, "raw": { "id": "001Dp00000P8QurIAF", "firstname": "Sally", "lastname": "Jones", "pronoun_custom_field": "she" // ... other fields for this record } }, { "fields": { "id": "001Dp00000P9BusEIS", "firstname": "Taylor", "lastname": "Lao" }, "mappedFields": { "pronoun": "they" }, "raw": { "id": "001Dp00000P9BusEIS", "firstname": "Taylor", "lastname": "Lao", "pronoun_custom_field": "they" // ... other fields for this record } } ] } ``` ### Example webhook message (URL) ```JavaScript JavaScript theme={"dark"} { "action": "read", "projectId": "ampersand-project-id", "provider": "salesforce", "groupRef": "xyz-company", "groupName": "XYZ Company", "consumerRef": "user-123", "consumerName": "John Doe", "installationId": "installation-id", "installationUpdateTime": "2024-12-07T00:20:36Z", "objectName": "Contact", "rawObjectName": "Contact", "workspace": "salesforce-subdomain", "operationId": "operation-uuid", "operationTime": "2024-12-07T00:20:40Z", "readType": "backfill", "resultInfo": { "type": "url", "downloadUrl": "https://storage.googleapis.com/....", "numRecords": 1 } } ``` ## Subscribe action webhooks Subscribe action webhooks look very similar to read action webhooks with a few additional fields in each result entry: * **subscribeEventType**: this is the normalized event type string, which can be: `create`, `update`, `delete`, `associationUpdate` (for HubSpot only). * **providerEventType**: this is the raw event type string from the API provider. * **rawEvent**: when available, this is the raw webhook event from the API provider. Here is a sample message: ```JavaScript JavaScript theme={"dark"} { "action": "subscribe", "projectId": "948234676-4874-43a4-beea-fa8081d13a07", "provider": "salesforce", "groupRef": "webhook-demo-group-id", "groupName": "webhook-demo-group-name", "consumerRef": "user-123", "consumerName": "John Doe", "installationId": "0c9230e1c-8fbe-4b28-bf10-2beee8fbf4ce", "installationUpdateTime": "2025-04-10T23:00:52.618406Z", "objectName": "company", "rawObjectName": "account", "workspace": "my-salesforce-workspace", "result": [ { "fields": { "annualrevenue": null, "id": "001Dp00000ZDgmxIAD", "website": null }, "mappedFields": { "accountName": "johnAmp", "notes": "Notes about the account" }, "subscribeEventType": "update", "providerEventType": "UPDATE", "raw": { "AnnualRevenue": null, "Description": "Notes about the account", "Id": "001Dp00000ZDgmxIAD", "Name": "johnAmp", "Website": null, "attributes": { "type": "Account", "url": "/services/data/v59.0/sobjects/Account/001Dp00000ZDgmxIAD" } }, "rawEvent": { "ChangeEventHeader": { "changeOrigin": "com/salesforce/api/soap/63.0;client=SfdcInternalAPI/", "changeType": "UPDATE", "changedFields": [ "Description", "LastModifiedDate" ], "commitNumber": 12041091891110, "commitTimestamp": 1744327345000, "commitUser": "005Dp000003Cd1SIAS", "entityName": "Account", "recordId": "001Dp00000ZDgmxIAD", "sequenceNumber": 1, "transactionKey": "00051705-2e99-d1e5-7e7a-48e3af682d07" }, "Description": "Notes about the account", "LastModifiedDate": "2025-04-10T23:22:25.000Z" } } ], "resultInfo": { "numRecords": 1, "type": "inline" } } ``` ## Handling webhook results Here's some pseudo-code to illustrate how you can parse the result from a webhook: ```go go theme={"dark"} // Generated from the OpenAPI spec type WebhookMessage struct { Action string `json:"action"` GroupName string `json:"groupName"` GroupRef string `json:"groupRef"` ... } func handleWebhookEndpointMessage(message WebhookMessage) { // Assumes that you have already parsed the webhook message into the openapi.WebhookMessage type var results map[string]any switch message.ResultInfo.Type { case "url": // If result type is "url", download the data res, err := http.Get(message.ResultInfo.DownloadUrl) if err != nil { // handle error } results = parseBodyIntoMap(res.Body) case "inline": // If result type is "inline", data is directly in the message results = message.Result default: // This should not happen - contact us if it does! } fmt.Printf("Received %d records", message.ResultInfo.NumRecords) // Process results as needed } ``` ### Handling the payload size The maximum size of a webhook payload Ampersand may send to your destination is **300 KB**. Most API gateways can handle this by default. If you implement your webhook using AWS Lambda or Google Cloud Run Functions, they will also be able to handle this payload size without any additional configuration. If you are using the Node.js framework [Express](https://expressjs.com) in your webhook receiver, and are using the [json middleware](https://expressjs.com/en/api.html#express.json) to parse request bodies, you will need to modify it to increase the maximum request body size. ```js theme={"dark"} const app = express(); app.use([ express.json({ limit: "350kb", // Set the max to 350 KB to allow for some buffer. }), ]); ``` ### Handling the message frequency By default, Ampersand will send you at most 1000 webhook messages per second across all your destinations. If you wish to change this limit, please contact `support@withampersand.com`. Alternatively, you can set the delivery mode to [on request](/read-actions#on-request-deprecated) to only get webhook messages when you request for them via an API call. ## Webhook signature verification Every webhook message that Ampersand sends includes a cryptographic signature. This signature lets you confirm that the payload is both authentic and unchanged when it reaches your system. Ampersand uses Svix to deliver webhooks to your destination, which is why the signature is sent in the `svix-signature` header. Each destination has its own unique secret, called the "webhook signing secret", which is used to generate the signature. To verify a webhook, you will need to: 1. [Get the signing secret](#viewing-your-secret) for your destination from the Ampersand dashboard. 2. Use that secret with any of the Svix libraries to verify the `svix-signature` header and confirm the message is valid. You can find more information on how to do this [here](https://docs.svix.com/receiving/verifying-payloads/how). #### Viewing your secret To find a destination's webhook signing secret, navigate to the destination in the Ampersand dashboard and click on **"Click here to view the secret"**. 1. Log in to the [Ampersand dashboard](https://dashboard.withampersand.com) & navigate to **[Destinations](https://dashboard.withampersand.com/projects/_/destinations)**. 2. Click on the destination you want to view the secret for. 3. Click on **"Click here to view the secret"**. Alt text #### Rotating your secret If you need to rotate your signing secret for security reasons, you can do so from the Ampersand dashboard. 1. Navigate to the **[Destinations](https://dashboard.withampersand.com/projects/_/destinations)** section in the Ampersand dashboard & click on the destination you want to rotate the secret for. 2. Click on **"Click here to view the secret"**. 3. Click on **"Rotate secret"** to rotate the secret & confirm the rotation. # Dev and prod environments Source: https://docs.ampersand.ai/dev-and-prod-environments Set up separate dev and prod projects, deploy one amp.yaml to each with the amp CLI, reuse destination names, and automate it with the Github Action. You can create multiple projects per organization, so we recommend at least creating two projects: * one for your production environment. * one for your development environment, where you can test integrations before shipping them to customers. You can deploy the same set of integrations to all of your projects by defining a single `amp.yaml` file. Then you can use the amp CLI to deploy it to all of your projects: ```bash theme={"dark"} amp login # First deploy to dev amp deploy my-integrations-folder --project=my-dev-project # Promote to prod when you are ready amp deploy my-integrations-folder --project=my-prod-project ``` Please note that for each of your projects, you will need to: * Add Provider Apps to the Ampersand Dashboard. See [Provider Guides](/provider-guides/overview) for more details. Do not reuse the same Provider App across projects, as this will lead to OAuth tokens in one project being invalidated when the same SaaS credential is used to generate tokens in another project. * Generate API keys for embedding UI components or for making API calls. * Add [Destinations](/destinations) to the Ampersand Dashboard if you have any read actions. ## Destination names You should use the same names for the webhook destinations in each of your projects. For example, `my-dev-project` can have a `hubspotWebhook` which has the URL `https://www.my-dev-webhook.com`, and `my-prod-project` can have a `hubspotWebhook` which has the URL `https://www.my-prod-webhook.com`. Then in my `amp.yaml` file, my integration will be defined as follows: ```yaml theme={"dark"} specVersion: 1.0.0 integrations: - name: readHubspotContacts provider: hubspot module: crm read: objects: - objectName: contact # In dev, webhook messages will go to `https://www.my-dev-webhook.com`, # In prod, they will go to `https://www.my-prod-webhook.com`. destination: hubspotWebhook ... ``` ## Deploy to multiple environments with Github Action With the [official Ampersand Github Action](https://github.com/marketplace/actions/official-ampersand-github-action), you can automate promotions between your different environments. Here is a [sample Github workflow](https://github.com/amp-labs/demo-hubspot-apollo/blob/main/.github/workflows/amp_deploy.yml) that deploys to a dev project on commits to a Pull Request, and deploys to a prod project once a Pull Request is merged to the main branch. # Prebuilt UI components Source: https://docs.ampersand.ai/embeddable-ui-components Install @amp-labs/react, set up AmpersandProvider with an API key or JWT, embed the install integration and connect provider components, and style them. ## Project setup ### Prerequisites * You need a React app to embed the components into. If you don't already have one, you can use the [Ampersand starter project](/starter-project). * The Ampersand UI library requires **React v18+**. ### Install the Ampersand React library In your repo, use `npm` or `yarn` to install the package: ``` npm install @amp-labs/react ``` or ``` yarn add @amp-labs/react ``` ## AmpersandProvider The `AmpersandProvider` is the foundational component that provides context for all Ampersand functionality, including both headless hooks and prebuilt UI components. It can be placed at any level in your component tree, but it must wrap all Ampersand components and hooks in your application. The `AmpersandProvider` accepts an `options` object with the following required properties: * `project`: Your Ampersand project name or ID (found on your [General Settings page](https://dashboard.withampersand.com/projects/_/settings)) * Authentication method: Either `apiKey` or `getToken` (but not both) ```tsx theme={"dark"} import { AmpersandProvider } from '@amp-labs/react'; import '@amp-labs/react/styles'; const options = { project: 'PROJECT', // Your Ampersand project name or ID // Pick one of the following authentication methods: apiKey: 'API_KEY', // Ampersand API key (simple) // OR getToken: async ({consumerRef, groupRef}) => { // JWT Authentication (advanced) // Custom logic to fetch JWT token from your backend, e.g. return await getTokenFromMyBackend(consumerRef, groupRef); }, }; function App() { return ( {/* All your Ampersand components and hooks go here */} ); } ``` ### Authentication Methods The `AmpersandProvider` supports two authentication methods through the `options` parameter. Only one method can be used at a time. #### API key authentication When you are first developing integrations with Ampersand, API keys are a quick way to get started. You can create an API key on the [API keys page](https://dashboard.withampersand.com/projects/_/api-keys) of your Ampersand Dashboard. Select "UI Library" for "Where will you use the API key?" #### JWT authentication When you are ready to ship your integrations to production, we highly recommend that you move to JWT authentication, as it is more secure than using API keys in the frontend. JWT tokens are time-bound and also enforce that your users only have access to their own integration data. ```tsx theme={"dark"} const options = { project: 'PROJECT', getToken: async ({consumerRef, groupRef}) => { // Custom logic to fetch JWT token from your backend, e.g. return await getTokenFromMyBackend(consumerRef, groupRef); }, }; return ( {/* All your Ampersand components and hooks go here */} ); ``` The `getToken` function should return a promise that resolves to a valid JWT token by making a call to your backend. For detailed information on how to generate JWT tokens, see [JWT Authentication](/api/jwt-auth). ## Components ### Install integration The `InstallIntegration` component prompts your customer for their SaaS credentials, and then guides them through the installation flow for an integration. If they've already installed this integration, then the component will display the current configuration of the integration and allow them to update it. Please note that each [group](/terminology#group) is able to install the integration once, so if someone else with the same `groupRef` has already installed the integration, then the user will not be able to install the same integration again. The parameters of the component are: * **integration** (string): the name of an integration that you've defined in `amp.yaml`. * **consumerRef** (string): the ID that your app uses to identify this end user. * **consumerName** (string, optional): the display name for this end user. * **groupRef** (string): the ID that your app uses to identify a company, team, or workspace. See [group](/terminology#group). * **groupName** (string, optional): the display name for this group. * **onInstallSuccess** (function, optional): a callback function that gets invoked after a consumer successfully installs the integration. If the integration has [always-enabled objects](#always-enabled-objects), this fires right after the consumer connects. * **onUpdateSuccess** (function, optional): a callback function that gets invoked after a consumer successfully updates an existing integration with the new configuration. * **onUninstallSuccess** (function, optional): a callback function that gets invoked after a consumer successfully uninstalls the integration. * **fieldMapping** (JSON): a JSON object that specifies the dynamic mapping configuration for this installation. Learn more about dynamic mapping [here](/object-and-field-mapping#dynamic-field-mappings) Both `onInstallSuccess` and `onUpdateSuccess` should be functions with the following signature: `(installationId: string, config: Config) => void`. `Config` is an exported type from `@amp-labs/react`. ```JSX theme={"dark"} console.log(`Successfully installed ${installationId}` + `with configuration ${JSON.stringify(configObject, null, 2)}`) } onUpdateSuccess = {(installationId, configObject) => console.log(`Successfully updated ${installationId}` + ` with configuration ${JSON.stringify(configObject, null, 2)}`) } onUninstallSuccess = {(installationId) => console.log(`Successfully uninstalled ${installationId}`) } fieldMapping={{ contacts: [ { mapToName: 'priority', mapToDisplayName: 'Priority', prompt: 'Which field do you use to track the priority of a deal?', }, ... // other field mapping options ], }} /> ``` ConnectProvider initial screen InstallIntegration configuration screen #### Always-enabled objects When your integration has [read objects marked `enabled: always`](/read-actions#always-enabled-objects), the component installs them for the consumer, since the consumer can't opt out of them: * **As soon as the consumer connects**, the component creates the installation with those objects, and `onInstallSuccess` fires. Reads and backfills for them start right away, even if the consumer leaves before configuring anything else. * **When the consumer opens an existing installation** that doesn't have one of those objects, the component adds it, and `onUpdateSuccess` fires. * After that, the consumer only updates the installation. They can pick optional fields and mappings for these objects, but the component doesn't offer a way to stop reading them. * If installing them fails (for example, the [sample read](/read-actions#install-time-validation) is rejected because the connected user can't access the object), the component shows the error with a **Retry** button, and the consumer can't continue until it succeeds. If you close your install dialog in `onInstallSuccess`, it will close right after the consumer connects when the integration has always-enabled objects. Use `onUpdateSuccess`, or keep the dialog open until the consumer finishes, if you want them to configure the rest of the integration first. #### Wizard mode (Beta) The wizard mode replaces the standard configuration view with a guided, multi-step installation flow during initial set up. It walks your end users through connecting, selecting objects, configuring fields/mappings, and reviewing before creating an installation. The wizard mode is currently in beta preview. The `variant="wizard"` prop is hidden and does not appear in the public TypeScript declarations as of `@amp-labs/react` v2.13.1. See the supported features table below for details on what is currently available. To activate wizard mode, pass `variant="wizard"` to the `InstallIntegration` component: ```JSX theme={"dark"}