Quick Start: Developing Your First Adapter
Introduction
In this Quick Start, we're going to build a simple Output Adapter that receives shipment data and outputs a text file with the mode of transportation. This is an oversimplified use case, and your production adapter will probably do a lot more work, however the purpose of this adapter is to show you how to interact with Chain.io and register your adapter.
Business Requirements
Our adapter has the following requirements
- Take a shipment as the input
- Allow the user to configure a transformation table to adjust the mode of transportation if they want to
- Give the user the option to fail or continue if the mode of transportation is not in our table
- Return the file with the mode of transportation
- Provide a user log message to the user letting them know what we did
- Register this adapter so it's available as an option as a "Visibility - Receive Shipment Updates" output adapter
Adapter Sample Code
This sample code represents a plain javascript implementation of our webhook and callback. This could be built into an ExpressJS server or be implemented on a hosted platform like AWS Lambda.
const CryptoJS = require('crypto-js')
// Do NOT commit the shared secret into your code repository
// Use whatever secrets manager you feel is appropriate to your
// development process to inject it into your code base.
const MY_CHAINIO_SECRET = process.env.CHAINIO_SHARED_SECRET
function makeSignature(body) {
const hash = CryptoJS.HmacSHA256(body, MY_CHAINIO_SECRET)
return CryptoJS.enc.Base64.stringify(hash)
}
function verifySignature(body, sig) {
const expected = makeSignature(body)
if (expected !== sig) {
throw new Error('bad signature')
}
}
async function processPayload (body, headers) {
verifySignature(body, headers['x-chainio-signature']
const userLogs = []
let status = 'success'
const { configuration, payload, callback } = JSON.parse(body)
// Extract configuration values from request that the user
// has configured in the Chain.io portal setup for the flow
const { modeTable, errorOnMissing } = configuration
// Extract the transport_mode property from the actual data payload
// Each payload will be unique to the type of integration, this
// particular example is using a visibility register shipment
// payload (vis.register_shipment)
const { transport_mode } = payload
let returnPayload = transport_mode
const conversion = modeTable.find(mt => mt.src === transport_mode)
if (conversion) {
returnPayload = conversion.dest
userLogs.push({
level: 'info',
message: `Converted ${transport_mode} to ${conversion.dest}`,
timestamp: Date.now().toISOString()
})
} else if (errorOnMissing) {
userLogs.push({
level: 'error',
message: `Source mode ${transport_mode} was not found, processing stopped`,
timestamp: Date.now().toISOString()
})
status = 'error'
} else {
userLogs.push({
level: 'info',
message: `Source mode ${transport_mode} was not found in conversion table, leaving it alone`,
timestamp: Date.now().toISOString()
})
}
const callbackVal = {
status,
userLogs,
callback // always return the callback property exactly as it was sent to you
// This sample does not include the optional dataTags & files objects
// you can learn more about them elsewhere in the documentation
}
if (status === 'success') {
callbackVal.payload = [
{
file_name: `mode-${Date.now()}.txt`,
mime_type: 'text/plain',
body: Buffer.from(returnPayload).toString('base64'),
content_transfer_encoding: 'base64'
}
]
}
const body = JSON.stringify(callbackVal)
return fetch('https://webhooks.chain.io/callback', {
method: 'post',
body,
headers: {
'Content-Type': 'application/json',
'x-chainio-signature': makeSignature(body)
}
})
}
Registering Your Adapter
In order for users to access your adapter, you'll need to register it using a developer key and our registration API.
// sample registration body
const options = {
method: 'POST',
headers: {
accept: 'application/json',
'content-type': 'application/json',
'x-api-key': process.env.CHAINIO_API_KEY //secret API key - DO NOT STORE IN YOUR SOURCE CODE
},
body: JSON.stringify({
integration_identifiers: ['vis.receive_shipment_update'],
display_name: 'Mode Text File Generator',
display_description: 'Make text file with your mode of transportation',
support_url: 'https://mymodetranslator.com/help',
adapter_type: 'output',
webhook_url: 'https://fake.fake/my_url',
shared_secret: 'abc1239193118$$$@!#1',
adapter_icon_url: 'https://cdn-icons-png.flaticon.com/512/9908/9908191.png',
publication_type: 'public',
configuration_options: [
{
name: 'customerNumber',
label: 'Customer Number',
support_url: 'https://docs.co2-co.sample/help#customerNumber',
description: 'Your CO2-Co customer number',
config_type: 'regex',
required: false,
regex_config: {
regex: '^[0-9]{5}$',
regex_error: 'Your customer number must be exactly 5 digits.'
}
},
{
name: 'errorOnMissing',
label: 'Error On Missing Mode',
description: "Should this job fail if the mode isn't in the table or just pass along the value provided.",
config_type: 'boolean'
}
]
})
};
fetch('https://webhooks.chain.io/adapters', options)
.then(response => response.json())
.then(response => console.log(response))
.catch(err => console.error(err));
Updated 8 months ago