# Business Logic

# Configuring the Webhook

Once you have the get_balance and account_transfer competencies built and trained, you are ready to integrate custom logic to fit your use case. All of the information the AI extracts from an utterance in the platform is mapped to aJSON payload that is then posted to a configured webhook endpoint.

To receive this payload in your application, you must expose an endpoint for the Clinc Platform to POST to. For example, https://your_domain.com/api/v1/clinc. For a simple solution for spinning up a server, check out Heroku (opens new window).

Once you have a deployed endpoint, paste its URL in the WEBHOOK URL field under your user settings (hover over your username on the top right of the nav bar and click Settings). Be sure to include https://.

You must also check the account_transfer and get_balance to be enabled for business logic. The platform will only POST to the webhook endpoint for competencies that are enabled. Make sure toClick "SAVE SETTINGS".

Set up your endpoint to just log the request body so you can get a sense of what information is coming through. The platform will ignore any 500 responses.

// ./server.js

app.post('/api/v1/clinc', (req, res) => {
    console.log(
        'XXXXXXXXXXXXXXXXX REQUEST DATA XXXXXXXXXXXXXXXXXXXX',
        JSON.stringify(req.body)
    );
    res.sendStatus(500);
});

Once you see the request data in your server logs, you're ready to implement some business logic.

# The Request Body

The request body coming from Clinc will look something like this:

{
    "qid": "6d090a7e-ba91-4b49-b9d5-441f179ccbbe",
    "lat": 42.2730207,
    "lon": -83.7517747,
    "state": "transfer",
    "dialog": "lore36ho5l4pi9mh2avwgqmu5mv6rpxz/98FJ",
    "device": "web",
    "query": "I want to transfer $400 from John's checking account to my credit card account.",
    "time_offset": 300,
    "session_info": {},
    "slots": {
        "_ACCOUNT_FROM_": {
            "type": "string",
            "values": [
                {
                    "tokens": "John's checking account",
                    "resolved": -1
                }
            ]
        },
        "_ACCOUNT_TO_": {
            "type": "string",
            "values": [
                {
                    "tokens": "credit card",
                    "resolved": -1
                }
            ]
        },
        "_TRANSFER_AMOUNT_": {
            "type": "string",
            "values": [
                {
                    "tokens": "$400",
                    "resolved": -1
                }
            ]
        }
    }
}

All types are string for now (dates, money, etc. coming).

# The Response Body

The POST endpoint has to respond with a JSON payload of the same shape. Only the state, slots, and session_info properties can be manipulated. Everything else is read-only and should remain the same in the response body. For this example, we will mutate the object in place (If you are familiar with function composition and immutable transformation patterns, that is the recommended way to go to effectively scale your business logic).

Tools for simpler manipulation are in the works, but for now, it's important to note how to traverse the JSON tree. Common tasks in business logic involve "resolving" a slot value and accessing the tokens value:

# Set _ACCOUNT_FROM_ to resolved: 1:

  1. Access the slots object
  2. Access the _ACCOUNT_FROM_ object
  3. Access the values array
  4. Access the first element in the values array
  5. Set the resolved to 1.

Here are some examples using JavaScript:

// using 'dot' notation
body.slots['_ACCOUNT_FROM_'].values[0].resolved = 1;

// using Object.assign (merges objects by mutating first object, second object overwrites)
Object.assign(body.slots['_ACCOUNT_FROM_'].values[0], {
    resolved: 1
});

# Access the tokens of the slot value

  1. Access the slots object
  2. Access the _ACCOUNT_FROM_ object
  3. Access the values array
  4. Access the first element in the values array
  5. Access the tokens object

Here are some examples using JavaScript:

// using 'dot' notation
body.slots['_ACCOUNT_FROM_'].values[0].tokens;

Before we get into connecting your application and the Clinc Platform, one strategy you can take to separate the HTTP interface from the data transformation is to build a function that takes a JSON payload as input and returns a modified version of that input. This will enable you to unit test your business logic without depending on the Clinc Platform.

# Transformation 1: Account Balance

Implement the following steps in your server language:

  1. Check to see if state equals account_balance
  2. If it does, check to see if there is a slot with the name _SOURCE_ACCOUNT_.
  3. If there is, check to see if the source type found in in the tokens key of _SOURCE_ACCOUNT_ is a valid account type.
  4. If it is, fetch the account balance for the corresponding source value. Add a new balance property to the _SOURCE_ACCOUNT_ slot. Set the balance value to the balance found in step 3.
  • If it is not a valid account type, set an error property to invalid account type.
  1. Set the resolved property on the _SOURCE_ACCOUNT_ slot object to 1.
  2. Return the body with the transformed _SOURCE_ACCOUNT_ object.
// ./lib/finance.js

const totallyRealAccounts = {
    checking: 100,
    savings: 500
};

const conversationResolver = body => {
    const { state } = body;

    if (state === 'get_balance') {
        const {
            slots: { ['_SOURCE_ACCOUNT_']: source }
        } = body;

        if (source) {
            console.log('Retrieving balance...');
            Object.assign(body.slots['_SOURCE_ACCOUNT_'].values[0], {
                resolved: 1
            });
            const { [source.values[0].tokens]: balance } = totallyRealAccounts;
            if (balance) {
                Object.assign(body.slots['_SOURCE_ACCOUNT_'].values[0], {
                    balance
                });
            } else {
                Object.assign(body.slots['_SOURCE_ACCOUNT_'].values[0], {
                    error: 'invalid account type'
                });
            }
            return body;
        }
    }
};

module.exports = conversationResolver;

# Transformation 2: Transfer Money

Implement the following steps in your server language:

  1. Check to see if state equals transfer_confirm
  2. If it does, add a _TRANSFER_ slot key to the body.slots with the following shape:
'_TRANSFER_': {
  resolved: 1,
  source: body.slots["_SOURCE_ACCOUNT_"].values[0].tokens,
  destination: body.slots["_DESTINATION_ACCOUNT_"].values[0].tokens,
  transferAmount: body.slots["_TRANSFER_AMOUNT_"].values[0].tokens,
}
  1. Validate the account types for _SOURCE_ACCOUNT_ and _DESTINATION_ACCOUNT_.
  2. If they are valid, transfer the _TRANSFER_AMOUNT_ from the _SOURCE_ACCOUNT_ to the _DESTINATION_ACCOUNT_.
    • If invalid, set an error property on the _TRANSFER_ slot object with an appropriate error message value.
  3. Set success and error properties on the _TRANSFER_ slot based on the result of the transfer function. For successful cases, error should be undefined/null/etc. and for error cases, success should be false or undefined.
  4. If the transfer is successful, set state to get_balance.
  5. Return the body with the transformed slots property that now has a _TRANSFER_ key and the transformed state property set to get_balance.
const totallyRealAccounts = {
    checking: 100,
    savings: 500
};

const transfer = ({ account, source, destination, amount }) => {
    if (account[source] > amount) {
        account[source] -= amount;
        account[destination] += amount;

        return { success: true };
    } else {
        return { success: false, error: 'insufficient funds' };
    }
};

const conversationResolver = body => {
    const { state } = body;

    if (state === 'get_balance') {
        // ...
    } else if (state === 'account_transfer_confirmed') {
        console.log('Initiating transfer...');

        const transferSlots = [
            '_SOURCE_ACCOUNT_',
            '_DESTINATION_ACCOUNT_',
            '_AMOUNT_'
        ];
        const [source, destination, amount] = transferSlots.map(
            slot => body.slots[slot].values[0].tokens
        );
        const transferData = { source, destination, amount: Number(amount) };

        body.slots['_TRANSFER_'] = {};
        Object.assign(body.slots['_TRANSFER_'], {
            values: [{ ...transferData, resolved: 1 }]
        });

        const invalidTypes = [source, destination].filter(
            account => !totallyRealAccounts.hasOwnProperty(account)
        );
        if (invalidTypes.length > 0) {
            Object.assign(body.slots['_TRANSFER_'].values[0], {
                success: false,
                error: `${invalidTypes}: invalid account type(s)`
            });
        } else {
            const { success, error } = transfer({
                ...transferData,
                account: totallyRealAccounts
            });

            if (success) {
                Object.assign(body.slots['_TRANSFER_'].values[0], { success });
                body.state = 'get_balance';
            } else {
                Object.assign(body.slots['_TRANSFER_'].values[0], { error });
            }
        }
        return body;
    }
};

module.exports = conversationResolver;

# Process POST Requests

Now that we have slot resolver logic in place, we can set up the endpoint to process requests from Clinc. The POST request handler should past the request body directly to the slot resolver function. If the result of the resolver is undefined/null/nil/etc., have the response send back a 500 error to have the Clinc Platform ignore the webhook results.

If the result is not undefined, merge the transformed body object with the original request object. The request should respond with the newly merged object that has any slot and/or state transformations.

// ./server.js

app.post('/api/v1/clinc', (req, res) => {
    console.log(
        'XXXXXXXXXXXXXXXXX REQUEST DATA XXXXXXXXXXXXXXXXXXXX',
        JSON.stringify(req.body)
    );

    const resolved = conversationResolver(req.body);

    if (resolved) {
        console.log(
            'XXXXXXXXXXXXXXXXX RESPONSE DATA XXXXXXXXXXXXXXXXXXXX',
            JSON.stringify({ ...req.body, ...resolved })
        );
        res.send(JSON.stringify({ ...req.body, ...resolved }));
    } else {
        // Clinc ignores any 400-500 responses
        res.sendStatus(500);
    }
});

You should now be able to run the following conversation in the query sidebar:

How much money do I have in checking?

<balance response>

Can I transfer $50 from there to savings?

<transfer confirmation>

Yes.

<show new checking account balance>.