How to use the webhook feature in the barKoder Demo App
Introduction#
The demo application, available on the iOS App Store and Google Play Store for Android, includes a feature allowing users to set up a webhook that triggers with each scan. This feature allows users to easily transmit scanned data to their server, ensuring a fast transfer of barcode information to their server-based application.
General settings #
The section related to webhooks is located at the bottom of the settings page within the app.
This section has three adjustable parameters that modify the behavior of the webhook feature and its configuration:
- Auto send to webhook
- Webhook confirmation feedback
- Encode webhook data
However, to enable the hook one must first configure it by tapping on webhook configuration.
Webhook configuration #
After tapping on the webhook configuration a dialog screen will ask the user for two(2) parameters:
webhook endpoint #
A URL that will receive the requests sent by the BarKoder app. The backend server that processes these requests can utilize any technology (Node.js, PHP, Python, etc.) as long as it can handle GET/POST requests.
secret word #
A secret key is a string used as a passkey to generate a token, named 'security_hash'. The server uses this token to authenticate requests, as explained further in the SERVER SECTION.
Confirmation and Encode Data #
Webhook Confirmation Feedback #
You can wait for confirmation from the server to notify the app about the status of the request.The app expects a JSON formatted response with the following structure, and if this is enabled it will show an alert screen.
The structure of the message constructed on the server side should be like this:
{
status:Boolean //(true or false if the actions succeeded)
message:String // String message describing the response
}
If you turn this off, the data will still be sent, but it won't wait for confirmation.
Encode webhook data #
It this is enabled the barcode data that is being sent to the server will be base64 encoded. It's up to the implementation on the server side if the server will expect encoded data or just plain data.
Pre-send procedure #
Before the app sends data to the webhook, it must prepare the request. This involves three major variables:
- security_data (Int) Unix Timestamp in seconds (10 digits)
- security_hash (String) This is a hash encoded with the basic MD5 algorithm. It is derived from the combination of security_data and secret_word.
- data (String) This is a JSON formatted string that carries the barcode information (type, value).
If "encode data" is selected, the data will be base64 encoded before the app sends it.
As previously noted, the security_hash variable is crucial. It's constructed from both security_data and the secret_word.
In summary, the app creates security_data as a UNIX timestamp. This timestamp is then concatenated with the secret word. The resulting string is then hashed using the MD5 algorithm.
Pseudo code:
let security_word = "VERY-SECRET-WORD"
let security_data = UNIX_TIMESTAMP
let security_hash = md5(security_data+security_word)
Swift Example
let securityData = String(format: "%.0f", Date().timeIntervalSince1970)
let securityHash = WebhookService.md5(string: "\\(securityData)\\(secretWord)")
let parameters: Parameters = [
"security_data": securityData,
"security_hash": securityHash,
"data": jsonData
]
let request = AF.request(
url,
method: .post,
parameters: parameters,
encoding: JSONEncoding.default
)
request
.validate()
.responseDecodable(of: WebhookResponse.self, completionHandler: { response in
switch response.result {
case .success(let response):
completion(.success(response))
case .failure(let error):
completion(.failure(error))
}
}
})
Server Setup #
- We extract the security_data, security_hash, and data parameters from the request body.
- We verify the security_hash by generating an MD5 hash using security_data concatenated with the secretWord.
- If the security hash matches, we proceed with processing the data (you would implement your own logic here).
- If the security hash doesn't match, we return a 400 Bad Request response with an error message.
Remember to replace "your_secret_word"
with your actual secret word used for hashing. Also, ensure that you are sending a JSON payload in the request body with the required parameters.
main : async function(req,res){
let post_data = req.body;
let query_data = req.query;
//here we grab both parameters sent by the APPlet security_data = post_data?.security_data ?? false;
let security_hash = post_data?.security_hash ?? false;
let data = post_data?.data ?? false
//if security_data is missing in the post, there is no point to continue
if(!security_data){
res.writeHead(403, {
'Content-Type': 'application/json'
})
res.end(JSON.stringify({ status:false,message:'Missing security_data!' }))
return;
}
//same for security_hash, if it's missing why go on?
if(!security_hash){
res.writeHead(403, {
'Content-Type': 'application/json'
})
res.end(JSON.stringify({ status:false,message:'Missing security_hash!' }))
return;
}
//now here we can do a basic comparation of the data being sent.
//what we want to achieve is the combination of the security_data that we receive
//and the secret_word that was inputted in the app.
//let's use crypto to create the hash
let crypto = require('crypto')
//ideally you'd have the secret key in an .env file away so the line would look something like this:
//let hash = crypto.createHash('md5').update(security_data + process.env.SECRET_WORD).digest("hex")
//however since this is just a simple showcase and not inteded to be used on a production server we can do:
let secret_word = 'this-is-my-very-secret-word";
let hash = crypto.createHash('md5').update(security_data + secret_word).digest("hex");
//this value of hash needs to match the value that we are being sent by the app stored in security_hash
if(security_hash === hash){
console.log('This is a valid request and we can do something with the data');
//here DO SOMETHING WITH THE DATA
res.writeHead(200, {
'Content-Type': 'application/json'
})
res.end(JSON.stringify({ status:true,message:'All good with the data, thanks!' }))
}
else{
console.log('The hash check failed to match, so we might want to deny this request');
res.writeHead(403, {
'Content-Type': 'application/json'
})
res.end(JSON.stringify({ status:false,message:'Forbidden for you!' }))
}
},
<?php
header('Cache-Control: no-cache, must-revalidate');
header('Expires: Mon, 26 Jul 1997 05:00:00 GMT');
header('Content-type: application/json');
$post_data = $_POST;
$security_data = (isset($_POST['security_data'])?$_POST['security_data']:false;
$security_hash = (isset($_POST['security_hash'])?$_POST['security_hash']:false;
$data = (isset($_POST['data'])?$_POST['data']:false;
if(!$security_data){
echo json_encode(array('status'=>false,'message'=>'security_data is missing'));
return;
}
if(!$security_hash){
echo json_encode(array('status'=>false,'message'=>'security_hash is missing'));
return;
}
if(md5($_POST['security_data'] . $privatekey) != $_POST['security_hash']){
echo json_encode(array('status'=>false,'status_type'=>'authentication error','message'=>'Sorry, not authenticated'));
return;
}
//At this point the request is valid and we can do stuff with the data that is sent from the app
$processed_data = array();
//... do something with $data
$data_array = json_decode($data);
//here the data will be of structure
//{
// symbology: 'EAN-13', //the type of the barcode
// value: 'NTA5OTIwNjA5OTE2Ng==', //the actual value of the barcode in this case its base64 encoded
// date: '1697690411' // the date, this should be cast into Int and then into a DateTime
//}
$data_array['value'] = base64_decode($data_array['value]);
$processed_data = $data_array;
echo json_encode(array('status'=>true,'message'=>'Your data was saved!','sent_data'=>$_POST,'processed_data'=>$processed_data));
<?php
$secretWord = "your_secret_word";
// Get request body
$requestBody = file_get_contents('php://input');
$data = json_decode($requestBody, true);
// Extract parameters
$security_data = $data['security_data'];
$security_hash = $data['security_hash'];
$data = $data['data'];
// Verify security hash
$hash = md5($security_data . $secretWord);
if ($hash !== $security_hash) {
http_response_code(400);
echo json_encode(['error' => 'Security hash mismatch']);
exit();
}
// Process data
// Your data processing logic here
http_response_code(200);
echo json_encode(['message' => 'Data processed successfully']);
const express = require('express');
const crypto = require('crypto');
const bodyParser = require('body-parser');
const app = express();
app.use(bodyParser.json());
const secretWord = "your_secret_word";
app.post('/processData', (req, res) => {
const { security_data, security_hash, data } = req.body;
// Verify security hash
const hash = crypto.createHash('md5').update(security_data + secretWord).digest('hex');
if (hash !== security_hash) {
return res.status(400).json({ error: 'Security hash mismatch' });
}
// Process data
// Your data processing logic here
res.status(200).json({ message: 'Data processed successfully' });
});
app.listen(3000, () => {
console.log('Server is running on port 3000');
});