data_intake Array
The data_intake array is optional and only used to define how to set up inbound data that is pushed (using a webhook) or pulled (using API polling) into ActiveCampaign from your system. The top-level data_intake array contains one or more data_intake objects. An application can define more than one data_intake, each dedicated to a specific inbound event. Each data_intake object describes how to manage inbound data for a specific event or external resource.
There are currently two types of data_intake objects supported, webhook and polling. A webhook-based data_intake defines how the ActiveCampaign platform will create, delete, and optionally update a webhook event in the system you are integrating. A polling-based data_intake is an alternative solution for executing API requests at scheduled intervals.
Webhook vs. PollingIf webhooks are available, this is the preferred integration method. Webhooks typically offer a more "real-time" experience and require less processing overhead. Only use polling if the system you are integrating does not support webhooks, or none of the existing webhooks support your integration use case.
Webhook
A webhook is a way of registering a callback between two systems. Events are pushed from one to another, often as they occur in near real-time. A data_intake defined as "type": "webhook" will create a new webhook URL in ActiveCampaign to accept incoming data. This URL is passed to the external API to register the webhook. When a webhook event occurs, the integrating system will post data to the registered webhook URL hosted at ActiveCampaign. A webhook data_intake object defines how to create, delete, and (optionally) update webhook registrations using your platform's API.
A webhook data_intake object contains the following properties and objects.
| Key | JSON Type | Required |
|---|---|---|
| name | string | yes |
| type | string | yes |
| scope | string | yes |
| resource_id | object | depends on scope |
| account_id | object | depends on scope |
| create | object | yes |
| update | object | no |
| delete | object | yes |
| verification | object | no - only if required by the third-party system |
The identifier for the data_intake object. This name must be unique across all data_intake objects within the same app. It is recommended to use the event name defined by the webhook API. For example, "name": "form_webhook" or "name": "event.account.created".
The type of data_intake object. For a webhook, the value must be set to "webhook".
The scope property defines the granularity of webhooks supported by the integrating system. The scope can be set to one of the following values.
| Scope | Description |
|---|---|
workflow | The system you are integrating supports one webhook per user account and event/resource. This is the most granular scope. |
connection | The system you are integrating supports one webhook per user account for all events/resources. |
application | The system you are integrating supports one webhook shared by all users of the integration. |
When a webhook scope is
connectionorapplication, additional information is required to process the incoming data.
The resource_id property is required for webhooks with a "connection" or "application" scope. It is a !jq comand that helps the integration locate the correct field within the webhook payload to use as the external resource id.
This value must be present in your webhook payload for our system to deliver data to the intended destination. The value of this field should match the resource id defined in the describe_selection.
The account_id property is similar to the resource_id but is only required for "application" webhooks that contain data for multiple users.
The create object defines how to create a webhook on your system so event data can be pushed to ActiveCampaign. The create object defines how the ActiveCampaign platform will call your system's API to register the webhook URL. The new URL created in ActiveCampaign to receive the inbound data is available using the ${webhook.url} variable.
In the following example, the create object uses the !pipe command to chain together several commands. The !http command is used to submit the webhook URL in the body of an HTTP POST. The !jq command parses the response returned by the API. The !save command is used to store the webhook id to use in the future to update or delete the webhook.
"create": {
"!pipe": [
"!http": {
"body": {
"url": "${webhook.url}"
},
"method": "POST",
"path": "/path/to/your/webhook/create/api"
},
{
"!jq": ".body | {webhook_id: .id}"
},
{
"!save": {
"scope": "workflow"
}
}
]
}The scope used in this example depends on the scope defined for the
data_intake. This value may be different for your system.
The update object defines how to update an existing webhook in your system. The associated webhook may need to be updated to continue receiving data, such as when a customer changes the workflow data mapping. It is optional and depends on the publisher API definition. The following example uses the webhook_id stored in the workflow scope from the previous create example.
"update": {
"!http": {
"method": "PUT",
"path": "/path/to/your/webhook/update/api",
"body": {
"id": "${data.workflow.webhook_id}"
}
}
}The delete step defines how to remove a previously created webhook to stop pushing unneeded event data to ActiveCampaign. The following example uses the webhook_id stored in the workflow scope from the previous create example.
"delete": {
"!http": {
"method": "DELETE",
"path": "/path/to/your/webhook/delete/${data.workflow.webhook_id}"
}
}Webhook verification is a way to evaluate the validity and authenticity of a webhook URL. This enables a third-party system to register and send webhook payloads with confidence. Many API's (including Asana, Meta, LinkedIn, Zoom, and WhatsApp) require validation and periodic re-validation to ensure that a webhook is genuine, and originates from ActiveCampaign.
Your CX application can support webhook verification by adding a verification section to your data_intake definition. For example, please see the sample LinkedIn webhook verification definition below:
"data_intake": [
{
"name": "event_lead_created",
"type": "webhook",
"scope": "application",
"verification": {
"_comment": "LinkedIn requires an initial manual handshake. Then will automatically revalidate every 2 hours",
"!switch": {
"jq": "if .query_params.challengeCode then 0 else 1 end",
"cases": [
{
"!pipe": [
{
"!jq": "{value_to_encrypt: .query_params.challengeCode}"
},
{
"!sha": {
"secret": "12345_thisIsASecret_54321",
"algorithm": "sha256"
}
},
{
"!respond": {
"code": 200,
"headers": {
"Content-Type": "application/json"
},
"body": {
"challengeCode": "${piped_content.1.value_to_encrypt}",
"challengeResponse": "${piped_content.2.hash}"
}
}
}
]
},
{
"_comment": "Process webhook payload as normal",
"!respond": {
"process_payload": true
}
}
]
}
}
}
]Types of webhook verification HTTP requests:
- Validation request: The first HTTP request to verify the webhook URL before registration.
- Re-validation request: The subsequent HTTP requests to verify the webhook URL after registration.
- Payload request: The HTTP request with the webhook payload, used to create the Contact in ActiveCampaign.
All three of the above HTTP requests send data to ActiveCampaign via the webhook URL, with subtle variations:
- Third-party system webhook URL validation flow leverage the application's client secret as the secret key along with a universally known hash algorithm such as the HMACSHA256 algorithm to generate and/or validate your CX application's response to a challenge code.
- Third-party systems normally send the challenge code as a part of the validation or re-validation request headers, query parameters, or body.
The verification step accepts any of the commands available. We recommend using the
!switchcommand with a!pipecontaining!jq,!shaand!respondcommands.
verificationObject Example Explanation:
- In the above-mentioned example LinkendIn verification object, we defined a
!switchcommand with a!pipecontaining!jq,!shaand!respondcommands. - You can access the challenge code value in the
jqproperty of the!switchcommand as follows:.headers.challengeCodewhen the challenge code is sent as a header.query_params.challengeCodewhen the challenge code is sent as a query.body.challengeCodewhen the challenge code is sent in the body
- Inside a
!pipecommand, use!jqcommand to extract thevalue_to_encrypt(challenge code) from the incoming validation or re-validation request.
The challenge code key should match the key sent to the webhook URL.If
http://test-webhook-url?challenge_code=12345is the webhook URL, then the challenge code value can be accessed in the switch command’sjqproperty as:.query_params.challenge_code
- Use
!shacommand to generate the challenge code hash. - Use
!respondcommand to define the HTTP response expected by the third-party system.
Not all APIs or inbound sources support webhooks. A data_intake defined as "type": "polling" can request data from your system at regular intervals. A polling configuration has specific lifecycle events for state management, initial_setup, pre_poll, request, and post_poll. request is the only event that is required. The other events may be needed for polling to work correctly with your system.
Polling State Management
It is important to understand that polling is defined by lifecycle events in order to maintain a consistent state between events. State variables allow the platform to persist data between sync executions, as well as between its lifecycle events and request iterations.
With any of the following lifecycle events, the data returned from the last execution is examined, and if a polling_state key is found its data will be saved and made available to subsequent lifecycle events.
When writing to the polling_state key it is essential to include all known keys and their current value. Saving the polling_state replaces the entire object, not just individual keys.
Example of returning the current polling_state without change: { polling_state: ${polling_state} }
Example of returning an updated polling_state: { polling_state: { keyA: ${polling_state.keyA}, keyB: "New Value"} }
The current polling state can be accessed in your commands using the ${polling_state} variable. To access a specific value you stored in the polling state, for example, a key named last_update, you can use ${polling_state.last_update}.
Defines the type of data_intake. For API polling, the value must be set to "polling".
The unique identifier for the data_intake. It is recommended to include the name of the resource requested from your API. For example, "name": "import-customers".
Polling takes advantage of lifecycle events and a special polling_state object. The initial_setup command runs only once when a customer configures or resets a workflow that uses polling. The most common use of this command is to initialize any polling_state keys and values for a first run. In some cases, that may involve fetching information from your system. All data returned by initial_setup except for polling_state will be ignored.
"initial_setup": {
"!pipe": [
{
"!http": {
"method": "GET",
"url": "https://example.api/object/${custom_data::form_field.value}"
}
},
{
"!jq": ".body | { polling_state: { keyA: .keyA, keyB: \"fixed start value\" }}"
},
]
},Similar to initial_setup, pre_poll is used to prepare any polling_state data for the current execution cycle. This command will run once per cycle, before the request command. All data returned by pre_poll except for polling_state will be ignored.
"pre_poll": {
"!pipe": [
{
"!http": {
"method": "GET",
"url": "https://example.api/object/${custom_data::form_field.value}"
}
},
{
"!jq": ".body | { polling_state: { keyA: .keyA, keyB: ${polling_state::keyB}}}"
},
]
},The request is the foundation for the polling process. The request is used to:
- Make outbound requests, looping until an exit condition
- Update state variables for tokens/parameters based on each response
- Return a final result to be used by the application workflow
"request": {
"!pipe": [
{
"!http": {
"method": "GET",
"url": "https://example.api/object/${custom_data.form_field.value}?keyA=${polling_state.keyA}"
}
},
{
"!jq": ".body | { results: .data, polling_state: { keyA: .keyA, keyB: ${polling_state.keyB}, iterate: false} }"
},
]
},Because the request is iterable, a formatted object needs to be returned as the final command result. This object must return a results object, an iteration flag, and the updated polling_state object. In the previous example, a !jq command is used to create and return the required results object as the final step of the request.
results: A jq selection to return an array of data from the!httpresponse. Each item in the array will be passed to the workflow’sdata_pipline.iterate: Any jq logic that results in a boolean to let the execution know if more data needs to be fetched, such as a paginated response.polling_state: The updated polling state will be available to other commands and events.
Pagination parameters, syncTokens, and other response-driven changes to variables are to be self-managed in the state. Do not rely on the pagination mechanism defined in the top-level
apiobject, if any, for polling requests.
The request command will complete and re-execute until one of the following exit conditions is met.
response.iterate== false- Total requests executed in this sync cycle exceed 100
- Total of
response.results[]from combined requests in this sync cycle exceed 1,000 response.results[]is null or emptyresponse.resultsmatches the previous request
The post_poll configuration is run once per cycle after all request iterations have been completed. This lifecycle event is useful for cleanup, or perhaps an outbound request to notify a 3rd-party API endpoint. It could also be used to replicate the initial_setup for polls that don't maintain a long-term state. The format of post_poll is the same as pre_poll and initial_setup.
Updated 11 days ago
