Catch webhook node.js - javascript

I'm trying to catch a PUT/webhook request that is being made by the Aftership API in node.js. A PUT request is made each time a push notification is needed to be made, I am using Parse to send the notifications but I need some of the data from the webhook.
The header of the webhook looks like Content-Type: application/json And contains this data:
ts - UTC unix timestamp that the event occurred
event - the name of the event (for tracking update, the value will be
'tracking_update')
msg - details about the message for which the event occurred, in the
following format.
How would I go about getting the tracking number, slug and the value for token in the custom fields dictionary in node or js?
{
"event": "tracking_update",
"msg": {
"id": "53aa94fc55ece21582000004",
"tracking_number": "906587618687",
"title": "906587618687",
"origin_country_iso3": null,
"destination_country_iso3": null,
"shipment_package_count": 0,
"active": false,
"order_id": null,
"order_id_path": null,
"customer_name": null,
"source": "web",
"emails": [],
"custom_fields": {},
"tag": "Delivered",
"tracked_count": 1,
"expected_delivery": null,
"signed_by": "D Johnson",
"shipment_type": null,
"tracking_account_number": null,
"tracking_postal_code": "DA15BU",
"tracking_ship_date": null,
"created_at": "2014-06-25T09:23:08+00:00",
"updated_at": "2014-06-25T09:23:08+00:00",
"slug": "dx",
"unique_token": "xk7LesjIgg",
"checkpoints": [{
"country_name": null,
"country_iso3": null,
"state": null,
"city": null,
"zip": null,
"message": "Signed For by: D Johnson",
"coordinates": [],
"tag": "Delivered",
"created_at": "2014-06-25T09:23:11+00:00",
"checkpoint_time": "2014-05-02T16:24:38",
"slug": "dx"
}]
},
"ts": 1403688191
}

It can be done with Express framework, example:
var express = require('express'),
bodyParser = require('body-parser'),
app = express(),
port = 3000;
app.use(bodyParser.json());
app.post('/', function (req, res) {
var body = req.body;
var trackingNumber = body.msg.tracking_number;
var slug = body.msg.slug;
var token = body.msg.unique_token;
console.log(trackingNumber, slug, token);
res.json({
message: 'ok got it!'
});
});
var server = app.listen(port, function () {
var host = server.address().address
var port = server.address().port
console.log('Example app listening at http://%s:%s', host, port)
});
Here is the GIT repository, just clone it and do npm install and then npm start. The server will run on port 3000 :D
Note: I saw in Aftership Webhook's documentation, it said they will request POST HTTP method, not PUT so I create an example of post request. Just replace it with put if you want it to catch put request.

For inspecting webhooks data, I would suggest to store every request in database and then query database. As each request is different, easiest way would be creating API in sails.js (Node.js framework with easy to use ORM).
sudo npm install sails -g
sails new _project_name_
cd _project_name_
sails generate api Records
With last command, sails has generated controller and model to store your webhook data.
I suggest installing pm2 for running app. you can run it with
pm2 start app.js
Next you should configure your webhook in Aftership for following url:
YOUR_SERVER_IP:PORT/Records/create
you can inspect data by following url:
YOUR_SERVER_IP:PORT/Records/find
if you want to parse data, it can be done in RecordsController.js, for example:
Parsing: function(req, res) {
Records.find({}).exec(function(err, results) {
var output = [];
while (results.length) {
var result = results.pop();
//custom parsing goes here
//example:
output.push({
tracking_number: result.msg.tracking_number,
slug: result.msg.slug,
unique_token: result.msg.unique_token
});
}
return res.json(output);
});
},
You can call this method via following url:
YOUR_SERVER_IP:PORT/Records/Parsing
I have created project in Codeanywhere for demonstration
webhook endpoint is:
http://port-1337.zavtt4t8a0jm7vigncyo3txxmuhxgvix3yxk66pvydgqfr.box.codeanywhere.com/records/create
For inspecting data, just replace /create part of url to /find
git repo is here: https://github.com/dkatavic/webhook_for_aftership
you can just clone the project on your server and run it (or use my server for testing)

You can catch PUT request by
app.put('/someRouteToCatchWebHook', function(request, response) {
//webhook parsing goes here
});
(i'm sure that you use expressjs in your code - see http://expressjs.com/api.html#app.METHOD for details).
If the webhook data is in request body, you can use the https://www.npmjs.com/package/body-parser module for parsing it.

Related

Patch custom object with kubernetes javascrtip SDK

I'm using External Secrets to sync my secrets from azure. And now I need a programmatic way to trigger the sync. With kubectl the command is
kubectl annotate es my-es force-sync=$(date +%s) --overwrite
So, I try to use k8s js sdk to do this. I can success fully get the External Secret
await crdApi.getNamespacedCustomObject("external-secrets.io", "v1beta1", "default", "externalsecrets", "my-es")
However, when I try to update it with patchNamespacedCustomObject, it always tells me "the body of the request was in an unknown format - accepted media types include: application/json-patch+json, application/merge-patch+json, application/apply-patch+yaml"
Here's my code
const kc = new k8s.KubeConfig();
kc.loadFromString(kubeConfig);
const crdApi = kc.makeApiClient(k8s.CustomObjectsApi);
let patch = [{
"op": "replace",
"path": "/metadata/annotations",
"value": {
"force-sync": "1663315075"
}
}];
await crdApi.patchNamespacedCustomObject("external-secrets.io", "v1beta1", "default", "externalsecrets", "my-es", patch);
I am referring their patch example here
const options = {
"headers": {
"Content-type": k8s.PatchUtils.PATCH_FORMAT_JSON_PATCH
}
};
is still required.

Delete By Query API working as curl but not in Node-Red

Background: What I am trying to achieve is to delete multiple values from elastic using a single API call. Our app uses Node-Red to create the backend API's.
I am using below curl command to delete multiple doc id's and it is working like a charm. It deletes the docs found with id's xxxxx and yyyyy.
POST /tom-access/doc/_delete_by_query
{
"query": {
"terms": {
"_id": [
"xxxxx",
"yyyyy"
]
}
}
}
However, when I try to do the same via Node-Red (using a JavaScript function), I am getting below error.
{"error":{"root_cause":[{"type":"action_request_validation_exception","reason":"Validation
Failed: 1: query is
missing;"}],"type":"action_request_validation_exception","reason":"Validation
Failed: 1: query is missing;"},"status":400}
Here is what I have inside the Node-Red JavaScript function:
if (!msg.headers) msg.headers = {};
msg.req = {
"query": {
"terms": {
"id": [
"xxxxx",
"yyyyy"
]
}
}
};
msg.headers = {
"Content-Type": "application/json",
"Authorization" : "Basic xxxxxxxxxxxxxxxxxxxxx"
};
msg.method = "POST"
// New elastic
msg.url = "http://elastic.test.com/tom-access/doc/_delete_by_query";
return msg;
The next node makes an HTTP CALL using above msg object but results in the error mentioned above. I am new to Node-Red, JavaScript and Elastic as well. HEEELP!!!
The endpoint is probably expecting the query to be in the body of the requests.
You should be setting it under msg.payload not msg.req.

Javascript Mongoose Database 'Doubling' (REST API - Weird)

I'm new to Javascript and I'm having a recurring error with a REST API. It's a strange error (for me at least), so strange that I don't know what to call it. I'm calling it "Doubling", but it probably has a proper name, I just don't know what it is.
Anyway, the situation is this. I'm creating a REST API about cars. I'm using Javascript and the following node modules:
1) Express
2) Body-Parser
3) Mongoose
4) MongoDB
5) Mongod
The DB is very simple. It literally just lists the names of cars. Here's the code for it:
var express = require('express');
var bodyParser = require('body-parser');
var theAutos = require('./cardata');
var mongoose = require('mongoose');
var app = express();
app.use(express.static('public'));
app.use(bodyParser.urlencoded({ extended: false }));
app.use(bodyParser.json());
mongoose.Promise = global.Promise;
var promise = mongoose.connect('mongodb://localhost/car_test_project', {
useMongoClient: true,
});
promise.then(function(db){
console.log('DATABASE NOW CONNECTED!!');
}).catch(function(err){
console.log('CONNECTION ERROR', err);
});
//*****SCHEMA*****
var Schema = mongoose.Schema;
var carTestSchema = new Schema({
name: String,
});
var Car = mongoose.model('Car', carTestSchema);
//*****END SCHEMA*****
//*****CAR DATA 'FOR EACH' FUNCTION*****
theAutos.forEach(function(theAutos){
var newAutomobile = new Car(theAutos);
newAutomobile.save(function (err) {
if (err) {
return (err);
}
});
});
//*****END 'FOR EACH' FUNCTION*****
//******THE 'GET' CODE*****
app.get('/carurl', function(req, res){
console.log(3);
Car.find({}).exec(function(err, car){
console.log(2);
if(err) {
return res.status(500).send(err);
}
return res.json(car);
});
});
//******END THE 'GET' CODE*****
//*****POST COMMAND CODE*****
app.post('/carurl', function(req, res){
var addAnAuto = new Car(req.body);
addAnAuto.save(function(err, car) {
if(err) {
return res.status(500).send(err);
}
return res.status(201).json(car);
});
});
//*****END POST COMMAND CODE*****
app.listen(8000, function(){
console.log('I be listening!');
});
At the beginning, the DB only has one entry; Ferrari. I've put this in a separate file called cardata.js and then I 'require' that file in the 3rd line of the above code (that's what the var theAutos = require('./cardata'); refers to. Here's the code for that file:
module.exports = [
{name: "Ferrari"}
]
As you can see, very simple database with only one entry. Now, here's where things get WEIRD!
I go to Postman and make a GET request. Postman comes back with Ferrari. So far so good. Then I go to make a POST request. I ask for a second car to be added to the database, in this case a Bugatti. I make a second GET request and I see:
{
"_id": "123etc...",
"name": "Ferrari",
"__v": 0
}
{
"_id": "xyzetc...",
"name": "Bugatti",
"__v": 0
}
So it's adding Bugatti to the database. That's great, right? It's doing what it's supposed to.
Wrong!
See, I need to make sure the addition is permanent, right? So I go to my terminal and restart the database by typing node index.js. I then go back to Postman and make yet another GET request. I'm expecting to see just Ferrari and Bugatti, right? However what I actually see, is:
{
"_id": "123etc...",
"name": "Ferrari",
"__v": 0
}
{
"_id": "xyzetc...",
"name": "Bugatti",
"__v": 0
}
{
"_id": "123etc...",
"name": "Ferrari",
"__v": 0
}
WTF?!? Where did that extra Ferrari come from? I certainly didn't want it there. It's like the DB is loading the DB with my POST request and then loading the original DB (which just had Ferrari in it) on top! Hence "Doubling"
Now, you might be thinking, "Shank, you fool! Just use a drop command like Car.collection.drop(); to refresh the DB when you reload it!"
Trouble is, when I try that I lose my Bugatti!
What I need is to figure out a way to (a) Add my Bugatti and other cars to the database and (b) do so in such a way that when I restart the database it doesn't "Double" and add another Ferrari in there.
It's no exaggeration to say I'm absolutely DESPERATE for help at this point. I've been grappling with this for days and I've read countless online tutorials and I've not come up with ANYTHING that can help me.
Any advice you could give would be massively appreciated.
Cheers,
Shank.

Error serving HTML files from an Azure function

I am trying to open, read and return an HTML files using Azure functions. I am developing locally and the logs says that the function executed successfully however on the browser I am getting 500 internal server error. Am I doing something wrong in here?
const fs = require('fs');
const path = require('path');
const mime = require('../node_modules/mime-types');
module.exports = function (context, req) {
const staticFilesFolder = 'www/build/';
const defaultPage = 'index.html';
getFile(context, req.query.file);
function getFile(context, file) {
const homeLocation = process.env["HOME"];
if(!file || file == null || file === undefined){
context.done(null,{status:200,body:"<h1>Define a file</h1>",headers:{
"Content-Type":" text/html; charset=utf-8"
}});
}
fs.readFile(path.resolve(path.join(homeLocation, staticFilesFolder, file)),
(err, htmlContent) => {
if (err) {
getFile(context, "404.html");
}
else {
const res = {
status: 200,
body: htmlContent,
headers:{
"Content-Type": mime.lookup(path.join(homeLocation, staticFilesFolder, file))
}
}
context.done(null,res);
}
})
}
};
Note
I am sure that 404.html exists and index.html exists. When I log the contents of htmlContent it is giving the correct output.
functions.json
{
"disabled": false,
"bindings": [
{
"authLevel": "anonymous",
"type": "httpTrigger",
"direction": "in",
"methods":["get"],
"route":"home",
"name": "req"
},
{
"type": "http",
"direction": "out",
"name": "res"
}
]
}
Response on Chrome
If I removed "Content-Length" header the status code changes to 406.
Update 1 The code seems to be running normally on Azure Portal but it is not working when running it locally.
It looks like you are combining two methods of returning data from an http triggered function(context.res and context.done()): https://learn.microsoft.com/en-us/azure/azure-functions/functions-reference-node#accessing-the-request-and-response
Since you are using context.res, try removing context.done();
You are making an incorrect use of context.res, you shouldn't be overwriting it but instead leveraging the methods provided by the Response class provided in the Azure NodeJS worker. If you are using using VSCode you'll get intellisense for these methods. Otherwise see: https://github.com/Azure/azure-functions-nodejs-worker/blob/dev/src/http/Response.ts
Your code should look something like this instead.
context.res.setHeader('content-type', 'text/html; charset=utf-8')
context.res.raw(htmlContent)
Using context.res.raw or context.res.send will already perform the context.done call for you.
Make sure you use content-type=text/html; charset-utf8 instead of content-type=text/html or you'll trigger an issue with the returned content-type. Instead of returning content-type=text/html you end up getting content-type=text/plain which will fail to render your html.
Addressed on: https://github.com/Azure/azure-webjobs-sdk-script/issues/2053

Stripe webhook don't work, error 500

Hi I setting up my webhook build with express.js in this way (the webhook is hosted on Parse.com):
var Stripe = require('stripe');
Stripe.initialize('sk_test_...');
...
app.post("/stripe", function(request, response) {
console.log("Type: "+request.body.type);
console.log("Event id: "+ request.body.id);
// Retrieve the request's body and parse it as JSON
// Verify the event by fetching it from Stripe
Stripe.Events.retrieve(request.body.id, function(err, event) {
console.log("This string is not printed on the logs");
response.send(200);
});
});
Then the test request was this, generated with changing a test user subscription in the dashboard:
{
"id": "evt_16oKcXAEHouDaR4rj8m5AtcC",
"created": 1443043065,
"livemode": false,
"type": "customer.subscription.updated",
"data": {
"object": {
"id": "sub_71zTKb8dLfo3RX",
...
"start": 1443042949
}
},
"object": "event",
"pending_webhooks": 3,
"request": "req_72PRYW2FrgFkDr",
"api_version": "2015-04-07"
}
This is my webhook log:
E2015-09-23T22:36:40.864Z]v342 Ran custom endpoint with:
Input: {"method":"POST","url":"/stripe","headers":{"accept":"*/*; q=0.5, application/xml","cache-control":"no-cache","content- length":"1358","content-type":"application/json; charset=utf- 8","host":"sceglime.parseapp.com","user-agent":"Stripe/1.0 (+https://stripe.com/docs/webhooks)","version":"HTTP/1.1","x-forwarded-for":"54.241.31.102, 10.252.11.20","x-forwarded-port":"80","x-forwarded- proto":"http"}}
Result: success/error was not called
I2015-09-23T22:36:40.974Z]Type: customer.subscription.updated
I2015-09-23T22:36:40.975Z]Event id: evt_16oLqrAEHouDaR4rpAi8FWt2
E2015-09-23T22:36:43.246Z]v342 Ran custom endpoint with:
Input: {"method":"POST","url":"/stripe","headers":{"accept":"*/*; q=0.5, application/xml","cache-control":"no-cache","content-length":"1160","content-type":"application/json; charset=utf-8","host":"sceglime.parseapp.com","user-agent":"Stripe/1.0 (+https://stripe.com/docs/webhooks)","version":"HTTP/1.1","x-forwarded-for":"54.241.34.107, 10.252.7.139","x-forwarded-port":"80","x-forwarded-proto":"http"}}
Result: success/error was not called
I2015-09-23T22:36:43.332Z]Type: invoiceitem.created
I2015-09-23T22:36:43.333Z]Event id: evt_16oLqrAEHouDaR4rcj0syMGn
On stripe logs I get an error "500 pending". Why the webhook don't respond correctly?
Thanks

Categories