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:

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

  1. 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.

  2. Create a JSON file named my_zis_bundle.json.

  3. 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 JobSpec goes here"    }  }}

    You'll define the custom actions, ZIS flow, and JobSpec in the next steps.

  4. 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.

  5. 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.

  6. Replace the InitiateRefund placeholder with the following custom action definition. In the definition, replace "EXTERNAL_TARGET_URL" with your https://webhook.site/ 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.

  7. 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"        }      }    }  }},
  8. Replace the RefundRequestJobSpec placeholder with the following JobSpec 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 JobSpec tells ZIS to run the ZIS flow when it detects a User Activity Created event.

  9. 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"      }    }  }}
  10. Upload the bundle to ZIS.

    curl -X POST https://{subdomain}.zendesk.com/api/services/zis/registry/{integration}/bundles \  -u {email}:{password} \  -H "Content-Type: application/json" \  -d @my_zis_bundle.json
  11. Install the JobSpec 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}:{password}

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 https://webhook.site/ URL. The URL represents an external API used to start a refund.

  1. 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}:{password} \  -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": "john.do[email protected]"        }      ]    }  }'
  2. Navigate to your https://webhook.site/ 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 https://webhook.site/ URL. This logic applies regardless of the refund amount.

  1. 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 a membership of "premium".

    curl -X POST https://{subdomain}.zendesk.com/api/v2/user_profiles/events \  -u {email_address}:{password} \  -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]"        }      ]    }  }'
  2. Navigate to your https://webhook.site/ 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
  1. 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 a membership of "basic".

    curl -X POST https://{subdomain}.zendesk.com/api/v2/user_profiles/events \  -u {email_address}:{password} \  -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]"        }      ]    }  }'
  2. 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}:{password} \  -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://z3n-jamesrodewig.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.