Kicking off a ZIS flow with user activity events
In this tutorial, you'll use a user activity event from the Events API to kick off a ZIS flow. You can use the Events API to track user events that occur in a third-party system. For example, you can use the API to track purchases or website visits for a customer.
Disclaimer: Zendesk provides this article for instructional purposes only. The example integration is not meant for a production environment.
What you'll need
To complete this tutorial, you'll need the following:
-
Familiarity with ZIS. Before you start, complete the Building your first ZIS integration tutorial.
-
A registered ZIS integration. You can use the same integration you created in Building your first ZIS integration or register a new one. To register a new integration, see Registering the integration name.
-
A ZIS OAuth token for the integration. See Obtaining a ZIS OAuth token.
-
A free external target URL from RequestBin
Creating the integration
The ZIS integration you create routes refund requests for an online store named Acme. The store uses the Events API to track refund requests as user activity events. When the integration detects a User Activity Created event, it checks if the event is a refund request. If not, the integration takes no action.
If the event is a refund request, the integration checks the refund amount. If the amount is $100 or less, the integration posts the event's data to an external target. This target represents an external API used to start a refund.
If the refund amount is more than $100, the integration gets the related customer's profile. The integration then checks the customer's store membership level. If the customer has a premium membership, the integration starts a refund. Otherwise, the integration creates a Zendesk ticket for the refund request.
Note: ZIS may retry a ZIS flow multiple times. In a production environment, ensure the external refund API supports idempotency. Idempotency lets you retry a request without performing the same operation multiple times. For example, if an API request to start a refund fails due to a network error, ZIS will retry the request. Idempotency guarantees the refund is performed only once, regardless of the number of related API requests.
To create the integration
-
Create an OAuth connection named "zendesk". You'll use the connection to authenticate Zendesk API requests in the integration's ZIS flow.
In a typical setup, an admin uses a private Zendesk app to create OAuth connections. For this tutorial, you'll create the "zendesk" OAuth connection without an app.
To create the connection, follow the steps in Creating an OAuth connection for Zendesk. Then return here.
-
Create a JSON file named my_zis_bundle.json.
-
Add the following bundle skeleton to my_zis_bundle.json:
{
"name": "Example ZIS integration for custom activity events",
"description": "Approve and route refund requests",
"zis_template_version": "2019-10-14",
"resources": {
"GetUserProfile": {
"_placeholder_": "ZIS custom action definition goes here"
},
"CreateZendeskTicket": {
"_placeholder_": "ZIS custom action definition goes here"
},
"InitiateRefund": {
"_placeholder_": "ZIS custom action definition goes here"
},
"RefundRequestFlow": {
"_placeholder_": "ZIS flow goes here"
},
"RefundRequestJobSpec": {
"_placeholder_": "ZIS job spec goes here"
}
}
}
You'll define the custom actions, ZIS flow, and job spec in the next steps.
-
In my_zis_bundle.json, replace the
GetUserProfile
placeholder with the following custom action definition."GetUserProfile": {
"type": "ZIS::Action::Http",
"properties": {
"name": "GetUserProfile",
"definition": {
"method": "GET",
"path.$": "/api/v2/user_profiles/{{$.profile_id}}",
"connectionName": "zendesk"
}
}
},
When called, the action sends a POST request to the Zendesk Profiles API's Get profile by profile id endpoint.
-
Replace the
CreateZendeskTicket
placeholder with the following custom action definition."CreateZendeskTicket": {
"type": "ZIS::Action::Http",
"properties": {
"name": "CreateZendeskTicket",
"definition": {
"method": "POST",
"path.$": "/api/v2/tickets.json",
"connectionName": "zendesk",
"headers": [
{
"key": "Content-Type",
"value": "application/json"
}
],
"requestBody": {
"ticket": {
"subject.$": "$.subject",
"comment": {
"body.$": "$.comment_body"
},
"requester_id.$": "$.requester_id"
}
}
}
}
},
When called, the action sends a POST request to the Zendesk Support API's Create Ticket endpoint. The request body contains the ticket's subject, body text, and requester id.
-
Replace the
InitiateRefund
placeholder with the following custom action definition. In the definition, replace "EXTERNAL_TARGET_URL" with your RequestBin endpoint URL."InitiateRefund": {
"type": "ZIS::Action::Http",
"properties": {
"name": "InitiateRefund",
"definition": {
"method": "POST",
"url": "EXTERNAL_TARGET_URL",
"requestBody": {
"order_id.$": "$.order_id",
"refund_amount.$": "$.refund_amount",
"user_profile_id.$": "$.profile_id"
}
}
}
},
When called, the custom action sends a POST request to the external target URL. The request body contains a store order id, the refund amount, and the customer's profile id.
-
Replace the
RefundRequestFlow
placeholder with the following ZIS flow definition. In the definition, replace "INTEGRATION" with your integration key.The flow starts with a Choice state that filters User Activity Created events. Most states in the flow only run for events with a
user_activity.type
of "refund_request"."RefundRequestFlow": {
"type": "ZIS::Flow",
"properties": {
"name": "RefundRequestFlow",
"definition": {
"StartAt": "CheckEventSourceAndType",
"States": {
"CheckEventSourceAndType": {
"Type": "Choice",
"Choices": [
{
"Variable": "$.input.activity_event.user_activity.type",
"StringEquals": "refund_request",
"Next": "ConvertRefundAmountToNum"
}
],
"Default": "Finish"
},
"ConvertRefundAmountToNum": {
"Type": "Action",
"ActionName": "zis:common:transform:Jq",
"Parameters": {
"expr": ".activity_event.user_activity.properties.refund_amount | tonumber",
"data.$": "$.input"
},
"ResultPath": "$.refund_amount_num",
"Next": "CheckRefundAmount"
},
"CheckRefundAmount": {
"Type": "Choice",
"Choices": [
{
"Variable": "$.refund_amount_num",
"NumericLessThanEquals": 100,
"Next": "InitiateRefund"
}
],
"Default": "GetUserProfile"
},
"GetUserProfile": {
"Type": "Action",
"ActionName": "zis:INTEGRATION:action:GetUserProfile",
"Parameters": {
"profile_id.$": "$.input.activity_event.user_activity.profile_id"
},
"ResultPath": "$.customer",
"Next": "CheckForPremiumMembership"
},
"CheckForPremiumMembership": {
"Type": "Choice",
"Choices": [
{
"Variable": "$.customer.profile.attributes.membership",
"StringEquals": "premium",
"Next": "InitiateRefund"
}
],
"Default": "CreateZendeskTicket"
},
"CreateZendeskTicket": {
"Type": "Action",
"ActionName": "zis:INTEGRATION:action:CreateZendeskTicket",
"Parameters": {
"subject.$": "Refund request for order {{$.input.activity_event.user_activity.properties.order_id}}",
"comment_body.$": "Hi,\n I'm requesting a ${{$.input.activity_event.user_activity.properties.refund_amount}} refund for order {{$.input.activity_event.user_activity.properties.order_id}}.",
"requester_id.$": "$.input.activity_event.user_activity.user_id"
},
"Next": "Finish"
},
"InitiateRefund": {
"Type": "Action",
"ActionName": "zis:INTEGRATION:action:InitiateRefund",
"Parameters": {
"order_id.$": "$.input.activity_event.user_activity.properties.order_id",
"refund_amount.$": "$.input.activity_event.user_activity.properties.refund_amount",
"profile_id.$": "$.input.activity_event.user_activity.profile_id"
},
"Next": "Finish"
},
"Finish": {
"Type": "Succeed"
}
}
}
}
},
-
Replace the
RefundRequestJobSpec
placeholder with the following job spec definition. In the definition, replace "INTEGRATION" with your integration key."RefundRequestJobSpec": {
"type": "ZIS::JobSpec",
"properties": {
"name": "RefundRequestJobSpec",
"event_source": "sunshine",
"event_type": "activity.UserActivityCreated",
"flow_name": "zis:INTEGRATION:flow:RefundRequestFlow"
}
}
The job spec tells ZIS to run the ZIS flow when it detects a User Activity Created event.
-
Save my_zis_bundle.json. The file should now look like this:
{
"name": "Example ZIS integration for custom activity events",
"description": "Approve and route refund requests",
"zis_template_version": "2019-10-14",
"resources": {
"GetUserProfile": {
"type": "ZIS::Action::Http",
"properties": {
"name": "GetUserProfile",
"definition": {
"method": "GET",
"path.$": "/api/v2/user_profiles/{{$.profile_id}}",
"connectionName": "zendesk"
}
}
},
"CreateZendeskTicket": {
"type": "ZIS::Action::Http",
"properties": {
"name": "CreateZendeskTicket",
"definition": {
"method": "POST",
"path.$": "/api/v2/tickets.json",
"connectionName": "zendesk",
"headers": [
{
"key": "Content-Type",
"value": "application/json"
}
],
"requestBody": {
"ticket": {
"subject.$": "$.subject",
"comment": {
"body.$": "$.comment_body"
},
"requester_id.$": "$.requester_id"
}
}
}
}
},
"InitiateRefund": {
"type": "ZIS::Action::Http",
"properties": {
"name": "InitiateRefund",
"definition": {
"method": "POST",
"url": "EXTERNAL_TARGET_URL",
"requestBody": {
"order_id.$": "$.order_id",
"refund_amount.$": "$.refund_amount",
"user_profile_id.$": "$.profile_id"
}
}
}
},
"RefundRequestFlow": {
"type": "ZIS::Flow",
"properties": {
"name": "RefundRequestFlow",
"definition": {
"StartAt": "CheckEventSourceAndType",
"States": {
"CheckEventSourceAndType": {
"Type": "Choice",
"Choices": [
{
"Variable": "$.input.activity_event.user_activity.type",
"StringEquals": "refund_request",
"Next": "ConvertRefundAmountToNum"
}
],
"Default": "Finish"
},
"ConvertRefundAmountToNum": {
"Type": "Action",
"ActionName": "zis:common:transform:Jq",
"Parameters": {
"expr": ".activity_event.user_activity.properties.refund_amount | tonumber",
"data.$": "$.input"
},
"ResultPath": "$.refund_amount_num",
"Next": "CheckRefundAmount"
},
"CheckRefundAmount": {
"Type": "Choice",
"Choices": [
{
"Variable": "$.refund_amount_num",
"NumericLessThanEquals": 100,
"Next": "InitiateRefund"
}
],
"Default": "GetUserProfile"
},
"GetUserProfile": {
"Type": "Action",
"ActionName": "zis:INTEGRATION:action:GetUserProfile",
"Parameters": {
"profile_id.$": "$.input.activity_event.user_activity.profile_id"
},
"ResultPath": "$.customer",
"Next": "CheckForPremiumMembership"
},
"CheckForPremiumMembership": {
"Type": "Choice",
"Choices": [
{
"Variable": "$.customer.profile.attributes.membership",
"StringEquals": "premium",
"Next": "InitiateRefund"
}
],
"Default": "CreateZendeskTicket"
},
"CreateZendeskTicket": {
"Type": "Action",
"ActionName": "zis:INTEGRATION:action:CreateZendeskTicket",
"Parameters": {
"subject.$": "Refund request for order {{$.input.activity_event.user_activity.properties.order_id}}",
"comment_body.$": "Hi,\n I'm requesting a ${{$.input.activity_event.user_activity.properties.refund_amount}} refund for order {{$.input.activity_event.user_activity.properties.order_id}}.",
"requester_id.$": "$.input.activity_event.user_activity.user_id"
},
"Next": "Finish"
},
"InitiateRefund": {
"Type": "Action",
"ActionName": "zis:INTEGRATION:action:InitiateRefund",
"Parameters": {
"order_id.$": "$.input.activity_event.user_activity.properties.order_id",
"refund_amount.$": "$.input.activity_event.user_activity.properties.refund_amount",
"profile_id.$": "$.input.activity_event.user_activity.profile_id"
},
"Next": "Finish"
},
"Finish": {
"Type": "Succeed"
}
}
}
}
},
"RefundRequestJobSpec": {
"type": "ZIS::JobSpec",
"properties": {
"name": "RefundRequestJobSpec",
"event_source": "sunshine",
"event_type": "activity.UserActivityCreated",
"flow_name": "zis:INTEGRATION:flow:RefundRequestFlow"
}
}
}
}
-
Upload the bundle to ZIS.
curl -X POST https://{subdomain}.zendesk.com/api/services/zis/registry/{integration}/bundles \
-u {email}/token:{api_token} \
-H "Content-Type: application/json" \
-d @my_zis_bundle.json
-
Install the job spec to enable the integration.
curl -X POST "https://{subdomain}.zendesk.com/api/services/zis/registry/job_specs/install?job_spec_name=zis:{integration}:job_spec:RefundRequestJobSpec" \
-u {email}/token:{api_token}
Testing the integration
To test the integration, use the Events API to create a user activity event for a refund request. Then verify that the integration either starts a refund or creates a Zendesk ticket.
This tutorial provides steps for three test cases:
You can use a similar workflow to cover other test cases.
Test case 1: $100 refund
Based on the integration's logic, a refund request of $100 or less should send a request to your RequestBin endpoint URL. The URL represents an external API used to start a refund.
-
Use the following request to create a user activity event for a refund request. The event has a
refund_amount
of "100.00".curl -X POST https://{subdomain}.zendesk.com/api/v2/user_profiles/events \
-u {email_address}/token:{api_token} \
-H "Content-Type: application/json" \
-d '{
"event": {
"source": "online_order",
"type": "refund_request",
"properties": {
"order_id": "789",
"refund_amount": "100.00"
}
},
"profile": {
"name": "John Doe",
"source": "acme_store",
"type": "customer",
"attributes": {
"membership": "basic"
},
"identifiers": [
{
"type": "email",
"value": "[email protected]"
}
]
}
}'
-
Navigate to your RequestBin dashboard. You should see a JSON payload with the order id, refund amount, and the customer's profile id.
Test case 2: $200 refund with a premium membership
Based on the integration's logic, a refund request for a customer with a premium membership should send a request to your RequestBin endpoint URL. This logic applies regardless of the refund amount.
-
Use the following request to create the user activity event. The event has a
refund_amount
of "200.00". The profile associated with the event has amembership
of "premium".curl -X POST https://{subdomain}.zendesk.com/api/v2/user_profiles/events \
-u {email_address}/token:{api_token} \
-H "Content-Type: application/json" \
-d '{
"event": {
"source": "online_order",
"type": "refund_request",
"properties": {
"order_id": "456",
"refund_amount": "200.00"
}
},
"profile": {
"name": "Jennifer Doe",
"source": "acme_store",
"type": "customer",
"attributes": {
"membership": "premium"
},
"identifiers": [
{
"type": "email",
"value": "[email protected]"
}
]
}
}'
-
Navigate to your RequestBin dashboard. You should see a JSON payload with the order id, refund amount, and the customer's profile id.
Test case 3: $200 refund without a premium membership
Based on the integration's logic, a refund request creates a ticket in Zendesk if:
- The refund amount is more than $100 AND
- The customer doesn't have a premium membership
-
Use the following request to create the user activity event. The event has a
refund_amount
of "200.00". The profile associated with the event has amembership
of "basic".curl -X POST https://{subdomain}.zendesk.com/api/v2/user_profiles/events \
-u {email_address}/token:{api_token} \
-H "Content-Type: application/json" \
-d '{
"event": {
"source": "online_order",
"type": "refund_request",
"properties": {
"order_id": "123",
"refund_amount": "200.00"
}
},
"profile": {
"name": "Jane Doe",
"source": "acme_store",
"type": "customer",
"attributes": {
"membership": "basic"
},
"identifiers": [
{
"type": "email",
"value": "[email protected]"
}
]
}
}'
-
To verify the integration created a related ticket in Zendesk, search for the customer's email address as a requester.
curl https://{subdomain}.zendesk.com/api/v2/search.json \
-u {email}/token:{api_token} \
-G --data-urlencode "query=requester:[email protected]"
The response contains a ticket. The ticket's description contains the order id and refund amount.
{
"results": [
{
"url": "https://example.zendesk.com/api/v2/tickets/75.json",
"id": 75,
...
"subject": "Refund request for order 123",
"raw_subject": "Refund request for order 123",
"description": "Hi,\n I'm requesting a $200.00 refund for order 123.",
...
}
],
...
}
You also can verify the ticket exists using the Zendesk Support agent interface.