Inbound Events: How to add JSONata to CN Conversations

The previous chapter should give you a quick overview of the possibilities you get with JSONata transformations. Check the JSONata documentation for additional transformation and manipulation capabilities. In this chapter, we look at how to integrate JSONata into CN Conversations.

Clients may have activity from within their technical landscape that affect contact data and need to reflect that update in CN Conversations. In a similar way that CN Conversations sends notifications out to downstream partners via webhooks to reflect activity from within our platform, it can ingest upstream integrator data by polling upstream partner APIs or by having integrators use the Events API to push data onto CN Conversations (as an alternative to the Messaging API).

Typical events coming from external systems represent:

  • appointment data gathered
  • home checkin
  • kiosk checkin
  • work approved / declined
  • repair started
  • video processed
  • video sent
  • video viewed
  • checkout prepaired
  • payment ready
  • checkout finished

Event Ingress Architecture

With Event Ingress, the Event Producers(CN Conversations Client) use the Ingress APIs to send an event from within their technical landscape. Individual use cases will differ from client to client, but the outcome is to have a single point of contact with a single API call where CN Conversations will handle the orchestration of a set of defined business processes without requiring any further consumer involvement beyond sending the initial message.

checkin-flow

Ingress Event Types

What an integrator wants to send events to the Event Ingress API it first needs to specify an Event Type. This will identify to CN Conversations what business process to invoke with the provided payload. This document outlines what Event Types are and explains the available business processes which are invoked when messages arrive.

The general format of an event type is

<provider>.<stage>.<action>

Example:

tjekvik.checkin.invite

Provider is a single-word upstream provider name.

Stage represents the phase in the sales or after sales lifecycle, and can be one of:

  • appointment
  • checkin
  • workorder
  • payment
  • checkout

Action can be anything (typically a verb), and maps to status changes or similar events at the upstream provider.

Event Sequences drive Sales or Service Boards

Orchestrate sequences of events to drive conversations on the sales and service boards.

  ->appointment->
Appointment
  ->checkin->
Consultation
  ->workorder->
Order Processing
  ->payment->
Vehicle Ready
  ->checkout->
Finished

Example sequence:

  • syntec.appointment.lead
  • ispa.appointment.ingress
  • tjekvik.checkin.invite
  • tjekvik.checkin.finished
  • planit.workorder.ready
  • cnvideo.workorder.ready
  • cnworkshop.payment.sent
  • cnworkshop.payment.received
  • tjekvik.checkout.start
  • tjekvik.checkout.finished

JSONata Transformation

The Event Type contains the JSONata transformation specification for the events of that type. Inbound event data is transformed into an Upsert conversations request, part of the Messaging API.

In this chapter, we will map the inbound external event to the POST /conversations endpoint. If the contact conversation doesn't exist yet, it will be created.

Example: tjekvik.checkin.invite

Step-by-Step Guide / Walkthrough

Use Case: send a checkin invite template

Tjekvik is an integrator that posts events, as per the Checkin integrator recipe.

Assumptions / prerequisites:

  • the contact is known and a contact conversation exists in CNC
  • the contact conversation has consumer properties like name, VIN and/or license plate entered
  • the contact conversation contains a current Workshop Appointment result

Message Template

The tjekvik.checkin.invite event sends a message template to the consumer, with an invitation to click a URL button to start the check-in process. This reminds the consumer of the pending workshop appointment and online checkin invitation. It should send out the following tjekvik_checkin_invite template configured in the dealer account:

Hi {{contact.name}}, please confirm your booking at our workshop for your vehicle with license plate {{data.MyCarLicensePlate}} on {{result.64b317386170235fe4b6f277.AppointmentDate}} at {{result.64b317386170235fe4b6f277.AppointmentTime}}. Save time by checking in from home.

The template also contains a call-to-action button:

Start Checkin
https://tjekvik.com/workorder/{{slug}}

Variables used in the template are known to the conversation (contact-, data and result prefixes) or provided as message.meta on message creation (the 'slug' variable). See Variables for more information.

Event Payload: tjekvik.checkin.invite

It is assumed that Tjekvik will send tjekvik.checkin.invite events like the following:

Request: POST https://api.web1on1.chat/v2/events/tjekvik.checkin.invite

Authorization: Bearer YOUR-TOKEN
Content-Type: application/json
{
    "BMWDealerId": "00027_1",
    "VIN": "WBA8E32070A544148",
    "variables": {
        "customer_hash": "C5334B8D"
    }
}

This event should send the templated message to the consumer (preferred channel MyBMW, fallback to WhatsApp).

Event Payload: tjekvik.appointment.create

The events will have a payload like the following:

Check-in invitations require an existing conversation with a workshop appointment result. During the initial test phase, it's helpful to programmatically create these conversations. To do this, we created the tjekvik.appointment.create event type (API call not shown here).

We assume the following simple Tjekvik-specific incoming payload for tjekvik.appointment.create is structured like:

Request: POST https://api.web1on1.chat/v2/events/tjekvik.checkin.invite

Authorization: Bearer YOUR-TOKEN
Content-Type: application/json
{
    "BMWDealerId": "00027_1",
    "VIN": "WBA8E32070A544148",
    "licensePlate": "MY-09-SP",
    "contact" : {
        "firstName": "John",
        "lastName": "Johnson",
        "email": "john@example.com",
        "phone": "+4411223344"
    },
    "variables": {
        "customer_hash": "C5334B8D",
        "appointmentDate": "2024-09-13",
        "appointmentTime": "15:00"
    }
}

The above payload adds a Results message to the conversation through a JSONata transformation.

Before You Start

Before you start, make sure you can do the following:

- Create an application integration (bot in bot store)
- Listen to incoming data
- Make some API requests using the bot token
- Listen to botinstance creations and deletions

Review the Application Integration recipe for more information

Step 1: Specify an event type

An event type specifies the transformation (mapping) of an external data payload into an API payload suitable for processing by CN Conversations. The internal payload should be the Upsert API call for conversations. It contains the contact or conversation identifier(s) and one or more messages to add to the conversation.

We'll create the tjekvik.checkin.invite event type. Leave the transformation specification field jsonata an empty object for now, and update it in the following step.

Request: POST https://api.web1on1.chat/v2/eventtypes

Authorization: Bearer YOUR-TOKEN
Content-Type: application/json
{
    "name": "tjekvik.checkin.invite",
    "description": "Tjekvik Checkin Invite",
    "jsonata": "{}"
}

Response:

{
    "id": "6663a1f684ba342816c4b7c4",
    "name": "tjekvik.checkin.invite",
    "description": "Tjekvik checkin",
    "jsonata": "{}"
}

To ensure a conversation with a workshop appointment exists before sending check-in invites, it's useful to create them programmatically. We will create the tjekvik.appointment.create event type for this purpose.

Request: POST https://api.web1on1.chat/v2/eventtypes

Authorization: Bearer YOUR-TOKEN
Content-Type: application/json
{
    "name": "tjekvik.appointment.create",
    "description": "Tjekvik Appointment",
    "jsonata": "{}"
}

Assume The id in the response is 66d85515e42cb6001ddd315b. This is used in example 2 below.

Step 2: Specify the JSONata transformation

To do the JSON to JSON transformation, we employ a utility called JSONata, which enables us to do the field mapping from external to internal data. To do so (and enable us to quickly iterate on the schema mapping), issue a PUT request to the event type.

Make sure the content-type is text/plain, and the resulting JSON matches a typical conversation Upsert request.

Example 1: tjekvik.checkin.invite to existing conversation

Request: PUT https://api.web1on1.chat/v2/eventtypes/6663a1f684ba342816c4b7c4/jsonata

Authorization: Bearer YOUR-TOKEN
Content-Type: text/plain
{
    "id": $conversationForVIN($organizationForBmwId(BMWDealerId), VIN),
    "messages": [
        {
            "type": "status",
            "text": "tjekvik.checkin.invite"
        },
        {
            "type": "chat",
            "text": "checkin invitation",
            "role": "agent",
            "meta": {
                "template": "tjekvik_checkin_invite",
                "slug": variables.customer_hash
            }
        }
    ]
}

JSONata Payload attributes:

  • Because we're adding to an existing conversation, we can specify a conversation ID. It can be derived from the event payload by combining the $conversationForVIN and $organizationForBmwId functions. See Special lookup functions for more information.
  • We send a status messase into the conversation, to inform agents.
  • We send the tjekvik_checkin_invite template for the conversation language (default: organzization.language)

Note: Either a conversation ID or an organization ID must be provided. If a conversation ID is not known, contact profile phone or email should be present, as in the first example.

Example 2: tjekvik.appointment.create to existing or new conversation

The tjekvik.appointment.create payload earlier adds a Results message to the conversation through the following JSONata transformation:

Request: PUT https://api.web1on1.chat/v2/eventtypes/66d85515e42cb6001ddd315b/jsonata

Authorization: Bearer YOUR-TOKEN
Content-Type: text/plain
{
    "id": $conversationForVIN($organizationForBmwId(BMWDealerId), VIN),
    "organization": $organizationForBmwId(BMWDealerId),
    "type": "contact",
    "contact": {
        "profile": {
            "givenName": contact.firstName,
            "familyName": contact.lastName,
            "email": contact.email,
            "telephone": contact.phone
        },
        "data": {
            "MyCarVIN": VIN,
            "MyCarLicensePlate": licensePlate
        }
    },
    "messages": [
        {
            "type": "field",
            "text": ".givenName " & contact.firstName
        },
        {
            "type": "field",
            "text": ".familyName " & contact.lastName
        },
        {
            "type": "field",
            "text": ".email " & contact.email
        },
        {
            "type": "field",
            "text": ".telephone " & contact.phone
        },
        {
            "type": "field",
            "text": ".MyCarVIN " & VIN
        },
        {
            "type": "field",
            "text": ".MyCarLicensePlate " & licensePlate
        },
        {
            "type": "results",
            "text": "tjekvik.appointment.create",
            "results": [{
                "name": "Appointment - Workshop",
                "form": "64b317386170235fe4b6f277",
                "values": {
                    "ServiceType": "Damage repair",
                    "AppointmentDate": variables.appointmentDate,
                    "AppointmentTime": variables.appointmentTime
                }
            }]
        }
    ]
}

Payload attributes:

  • organization is required to target the right CNC dealer account. It is derived from the native event request payload's BMWDealerId by the $organizationForBmwId function.
  • contact.profile should provide at least an email and/or phone number to find the matching contact.
  • data represents contact attributes (not used yet).
  • As messages, some contact attributes are set and a results message is created.





Step 3: Preview Your JSONata Output

To test the transformation of an event payload without actually processing the event, add inspect=true as a query parameter to the create-event endpoint. This dry-run will return the transformed payload as it would have been processed on an actual call.

Request: POST https://api.web1on1.chat/v2/events/tjekvik.checkin.invite?inspect=true

Authorization: Bearer YOUR-TOKEN
Content-Type: application/json
{
    "BMWDealerId": "00027_1",
    "VIN": "WBA8E32070A544148",
    "variables": {
        "customer_hash": "C5334B8D"
    }
}

This should return the POST /v2/conversations payload that would have been processed without the inspect flag:

{
    "id": "66c3ed372a33131f1b4ba1b8",
    "messages": [
        {
            "type": "status",
            "text": "tjekvik.checkin.invite"
        },
        {
            "type": "chat",
            "text": "checkin invitation",
            "role": "agent",
            "meta": {
                "template": "tjekvik_checkin_invite",
                "slug": "C5334B8D"
            }
        }
    ]
}

The inspect=true request parameter will show the Upsert conversation payload that is to be processed, without actually processing it - a dry-run if you will.

When combined with the PUT requests above, this will allow you to rapidly test and iterate on refining internal payloads for new event types.

Step 4: Start creating events

To create new events, and process the transformed payloads as conversations and messages in the system, post your external payloads to the right event handler for the event type.

Request: POST https://api.web1on1.chat/v2/events/tjekvik.checkin.invite

Authorization: Bearer YOUR-TOKEN
Content-Type: application/json
{
    "BMWDealerId": "00027_1",
    "VIN": "WBA8E32070A544148",
    "variables": {
        "customer_hash": "C5334B8D"
    }
}

It will return the conversation from the conversation upsert event, as well as any messages that were created after processing the tranformed document.

{
    "conversation": { ... }
    "contact": { ... }
    "messages": [ ... ]
}

If the request matches an existing conversation, because of a specified conversation ID or because of matching contact identifiers, it will return '200 OK'. If the upsert request did not match an existing conversation, a new conversation would have been created, returning the status code '201 Created'.

Refine Transformation Payloads

To further iterate on the transformation, provide further PUT requests to the jsonata endpoint, using the plain/text content type.

Example: tjekvik.checkin.finish

After the consumer has gone through the check-in process, Tjekvik can notify CNC that the check-in has completed throught the tjekvik.checkin.finish event.

Event payload

In this example, we presume Tjekvik will send the following kind of event payloads:

Request: POST https://api.web1on1.chat/v2/events/tjekvik.checkin.finish

Authorization: Bearer YOUR-TOKEN
Content-Type: application/json
{
    "BMWDealerId": "00027_1",
    "VIN": "WBA8E32070A544148",
    "variables": {
        "screenwash": "yes",
        "wiperblades": "yes",
        "jobDescription": "Check tires",
        "comment": "No comment"
    }
}

Create the Event type

Request: POST https://api.web1on1.chat/v2/eventtypes

Authorization: Bearer YOUR-TOKEN
Content-Type: application/json
{
    "name": "tjekvik.checkin.finish",
    "description": "Tjekvik checkin finished",
    "jsonata": "{}"
}

Response:

{
    "id": "66b9d64bba91ed001e85e517",
    "name": "tjekvik.checkin.finish",
    "description": "Tjekvik checkin finished",
    "jsonata": "{}"
}

Set the event type's JSONata transformation

the tjekvik.checkin.confirmation payload above adds a Check-In Results message to the conversation through the following JSONata transformation:

Request: PUT https://api.web1on1.chat/v2/eventtypes/66b9d64bba91ed001e85e517/jsonata

Authorization: Bearer YOUR-TOKEN
Content-Type: text/plain
{
   "id": $conversationForVIN($organizationForBmwId(BMWDealerId), VIN),
   "messages": [
        {
            "type": "results",
            "text": "tjekvik.checkin.finish",
            "results": [{
                "name": "Appointment - Check-in",
                "form": "64400aae0760c8001d967720",
                "type": "topic",
                "values": {
                    "ContactMethodPreference": "MyBMW",
                    "ContactDetailsCorrect": "yes",
                    "UpsellScreenwash": variables.screenwash,
                    "UpsellWiperBlade": variables.wiperblades,
                    "RequirementsComments": variables.jobDescription,
                    "GeneralComment": variables.comment
                }
            }]
        }
    ]
}

Start creating Events

With the JSONata field mapping set up, we can start creating checkin.finish events from the Tjekvik system:

Request: POST https://api.web1on1.chat/v2/events/tjekvik.checkin.finish (payload: see above)

This will create a Check-In Result in the conversation:

checkin-result

More info on the "Appointment - Check-in" form can be found in the Forms and Fields messaging chapter.

Orchestration by Dealerships

CN Conversations can map these external events to trigger Customer Journey processes using Event Routing Rules. To make the event type available to routing rules in dealer accounts, set the orchestrate boolean property to true.

This has the following advantages over sending messages through the messaging API:

  • No need to maintain the message content anymore; fire-and-forget. Just sending events is enough.
  • The dealer decides on what they want to happen for status updates (nothing, WhatsApp template, notification of users, starting a bot etc etc)

If the eventtype.orchestrate boolean is set to true, a dealer can trigger additional actions when the event type occurs.

checkin-routing-rule

In the example above, the dealer has configured that when a tjekvik.checkin.invite event occurs:

  • a user is notified
  • a form is added
  • a template is sent
  • the conversation is shown in a column on the service board