Stripe Webhook Signature Checks - Node js - IBM Functions / OpenWhisk - node.js

There is some kind of encoding issue when I try to verify the signature for a Stripe Webhook. I know it’s not an issue with the Stripe package itself because I get different signatures when trying to manually hash the body data and compare the HMAC-256SHA signature with the signature from Stripe in the headers. I have tried so many different things to so many different parts, that it’s possible I have had multiple mistakes.
You’re not able to see here, but the IBM Cloud Function has been set to pass raw HTTP data, and that’s why you the decoding function being used.
The webhook is successful without verifying the signatures.
The error generated by the Stripe event function is, “No signatures found matching the expected signature for payload. Are you passing the raw request body you received from Stripe?”
Note: Errors are not handled correctly here while trying to debug this issue.
const stripe = require('stripe')('sk_test_********’);
var crypto = require('crypto');
// tried with the CLI secret and the one from the dashboard.
const endpointSecret = 'whsec_****’;
// Convert the stripe signature in string format to a JSON object
function sig_conversion(data){
var sig_obj = {}
var data_list = data.split(",").map((x)=>x.split("="));
var data_json = data_list.map((x)=>{sig_obj[x[0]] = x[1]})
return sig_obj
}
function decode(args) {
var decoded = new Buffer.from(args.__ow_body, 'base64')//.toString('utf-8')
return {body: decoded}
}
function main(params){
//let sig = sig_conversion(params.__ow_headers['stripe-signature']);
let sig = params.__ow_headers['stripe-signature']
let signature = sig_conversion(params.__ow_headers['stripe-signature']);
//console.log(222, signature);
var data = decode(params);
let event;
// Trying to see the result from manually checking the signatures.
var signed_payload = data.body + "." + signature.t
var hmac = crypto.createHmac('sha256', endpointSecret);
var hmac_sig = hmac.update(signed_payload);
var gen_hmac= hmac_sig.digest('hex');
console.log(gen_hmac, 222, signature, 444)
try {
event = stripe.webhooks.constructEvent(JSON.parse(data.body), sig, endpointSecret);
//event = JSON.parse(data.body);
}

Here are some steps to help people trying the same thing (some of the steps are general steps not directly related to the problem from above).
Ensure that web actions are enabled under the Endpoints menu.
Check the option for Raw HTTP handling under the same screen (Most of the documentation you will see is in relation to using Node.js + Express. The error that people experience in Express is the same, which is that the raw signature data in the header and the body data is needed for the verifying the signature. This applies to regardless of whether you are using Stripe’s package or manually verifying the signatures.)
Process the body data from ‘base64’ encoding.
If the endpoint secret from the Stripe CLI tool doesn’t work, try the one from the dashboard; and vice-versa.
Note: People using Google Cloud Functions or Pub-sub will likely have similar issues with signature verification.
function decode(args) {
var decoded = new Buffer.from(args.__ow_body, 'base64')
return {body: decoded}
}
// Match the raw body to content type application/json
function main(params){
let sig = params.__ow_headers['stripe-signature']
var data = decode(params);
let event;
try {
event = stripe.webhooks.constructEvent(data.body, sig, endpointSecret);
}
// The rest is the same as the stripe boilerplate code.
catch (err) {
return {
body: {payload:''},
statusCode:200,
headers:{ 'Content-Type': 'application/json'}
};
}
// Handle the event
switch (event.type) {
case 'payment_intent.succeeded':
const paymentIntent = event.data.object;
console.log('PaymentIntent was successful!')
break;
case 'payment_method.attached':
const paymentMethod = event.data.object;
console.log('PaymentMethod was attached to a Customer!')
break;
// ... handle other event types
default:
// Unexpected event type
return {
body: {payload:''},
statusCode:200,
headers:{ 'Content-Type': 'application/json'}
};
}
// Return a response to acknowledge receipt of the event
return {
body: {payload:''},
statusCode:200,
headers:{ 'Content-Type': 'application/json'}
};
};

Related

Send proposal with signature ends with `ChaincodeId is nill` error in Hyperledger Fabric 2.2 NodeJS client

This is the flow we need on the backend.
First user creates unsigned proposal and the proposal buffer is returned to him.
const proposal = new Endorsement(this.config.chaincodeId, this.channel)
const user = User.createUser(
enrollmentId,
enrollmentId,
this.config.userMspId,
certificate
)
const identityContext = new IdentityContext(user, this.channel.client)
const proposalBuffer = proposal.build(identityContext, {
fcn,
args,
})
const digest = createHash('sha256').update(proposalBuffer).digest('hex')
Then after user signs digest and creates signature our backend sends signed proposal to endorser:
const signedProposal = {
signature: Buffer.from(signature, 'base64'),
proposal_bytes: proposalBuffer,
}
const endorser = this.channel.getEndorsers(this.config.userMspId)[0]
const response = await endorser.sendProposal(
Buffer.from(JSON.stringify( signedProposal ))
)
sendProposal method throws ChaincodeId is nil error.
Anyone knows how we could implement this right?
How do we create the Buffer object for the sendProposal method parameter?
In my case I created the buffer from stringified json object, how SignedProposal is defined in the Hyperledger Fabric documentation.
I see your code is custom code to send a proposal. Why should you do this? Why not do the easy way by using fabric-network lib?
For your question, I found some code in fabric-network:
// This is the object that will centralize this endorsement activities
// with the fabric network
const endorsement = channel.newEndorsement(this.contract.chaincodeId);
const proposalBuildRequest = this.newBuildProposalRequest(args);
logger.debug('%s - build and send the endorsement', method);
// build the outbound request along with getting a new transactionId
// from the identity context
endorsement.build(this.identityContext, proposalBuildRequest);
endorsement.sign(this.identityContext);
...
...
else if (this.endorsingOrgs) {
logger.debug('%s - user has assigned endorsing orgs %s', method, this.endorsingOrgs);
const flatten = (accumulator, value) => {
accumulator.push(...value);
return accumulator;
};
proposalSendRequest.targets = this.endorsingOrgs.map((mspid) => channel.getEndorsers(mspid)).reduce(flatten, []);
}
...
...
// by now we should have targets or a discovery handler to be used
// by the send() of the proposal instance
const proposalResponse = await endorsement.send(proposalSendRequest);
...
In send method:
...
const signedEnvelope = this.getSignedProposal();
...
peer.sendProposal(signedEnvelope, requestTimeout)
...
Look inside getSignedProposal method:
...
const fabproto6 = require('fabric-protos');
...
/*
* return a signed proposal from the signature and the payload as bytes
*
* This method is not intended for use by an application. It will be used
* by the send method of the super class.
* #returns {object} An object with the signature and the payload bytes
*/
getSignedProposal() {
const method = `getSignedProposal[${this.type}:${this.name}]`;
logger.debug('%s - start', method);
this._checkPayloadAndSignature();
const signedProposal = fabproto6.protos.SignedProposal.create({
signature: this._signature,
proposal_bytes: this._payload
});
// const signedProposal = {
// signature: this._signature,
// proposalBytes: this._payload
// };
return signedProposal;
}
So, try to use fabric-protos lib to encode your proposal. Hope this help
Solved this issue by moving to #hyperledger/fabric-gateway library. It works fine and has a well documented API. Offline transactions are better supported too.

How to Validate a Xero webhook payload with HMACSHA256 Node js

I need to validate Xero webhook in my node js project. This is Xero documentation steps to validate: https://developer.xero.com/documentation/webhooks/creating-webhooks#STATUS
var crypto = require("crypto")
function getHmacSha256(message, secret) {
return crypto.createHmac("sha256", secret).update(message).digest("base64")
}
// webhookPayload and signature get from webhook body and header
const webhookPayload = {
events: [],
firstEventSequence: 0,
lastEventSequence: 0,
entropy: 'OSHPXTUSXASRFBBCJFEN'
}
const signature = "OXLaeyZanKI5QDnLkXIVB35XrZygYsPMeK8WfoXUMU8="
const myKey = "1y5VYfv7WbimUQIMXiQCB6W6TKIp+5ZZJNjn3Fsa/veK5X/C8BZ4yzvPkmr7LvuL+yfKwm4imnfAB5tEoJfc4A=="
var hash = getHmacSha256(JSON.stringify(webhookPayload), myKey)
//If the payload is hashed using HMACSHA256 with your webhook signing key and base64 encoded, it should match the signature in the header.
if (signature === hash) {
return res.status(200).end()
}else{
return res.status(401).end()
}
Every time my signature and hash are different so it returns with 401 every time.
So I failed to complete Intent to receive
From what you're describing, my guess is you are unintentionally modifying the request body. You need to accept the raw request body from the webhook event without modification. If this body is modified at all, your code will fail to verify the signature and will fail Xero’s “Intent to receive” validation. Check out this blog post for details.

Twilio - Incoming calls not being processed (quickstart)

I've sign up to start using Twilio and I'm trying to setup the quickstart (https://www.twilio.com/docs/voice/client/javascript/quickstart) and it's almost working but incoming calls are not being received by:
Client code (used on browser after getTokenCapabilities):
Twilio.Device.incoming(function (conn) {
log('Incoming connection from ' + conn.parameters.From);
var archEnemyPhoneNumber = '+12093373517';
if (conn.parameters.From === archEnemyPhoneNumber) {
conn.reject();
log('It\'s your nemesis. Rejected call.');
} else {
// accept the incoming connection and start two-way audio
conn.accept();
}
});
Code on Twilio Function for voice calls (consoles are always printed and else condition is never called:
exports.handler = function(context, event, callback) {
let twiml = new Twilio.twiml.VoiceResponse();
console.log('entrou aqui');
if(event.To) {
console.log('entrou ali');
// Wrap the phone number or client name in the appropriate TwiML verb
// if is a valid phone number
const attr = isAValidPhoneNumber(event.To) ? 'number' : 'client';
const dial = twiml.dial({
callerId: context.CALLER_ID,
});
dial[attr]({}, event.To);
} else {
twiml.say('Thanks for calling!');
}
console.log('callback');
callback(null, twiml);
};
/**
* Checks if the given value is valid as phone number
* #param {Number|String} number
* #return {Boolean}
*/
function isAValidPhoneNumber(number) {
return /^[\d\+\-\(\) ]+$/.test(number);
}
I've include my phone number as Verified Caller ID, got a number from Twilio and create the functions using template Twilio Client Quickstart.
On Twilio Client Quickstart, i've paste TwiML SID as TWIML_APP_SID and tried to use my phone number and the number from Twilio as CALLER_ID.
Also I've changed VOICE URL on TwiML configuration and changed the VOICE URL on phone number from twilio configuration.
Any ideas on what is missing or what is wrong? When I open on browser http://127.0.0.1:8080/, It's possible to make calls but I don't receive any call on browser when I call to twilio number.
In order to answer call, you need to have your token name identity in a tag in your VoiceResponse, here is an example.
exports.incomingVoiceResponse = function voiceResponse( to ) {
// Create a TwiML voice response
const twiml = new VoiceResponse();
// Wrap the phone number or client name in the appropriate TwiML verb
// if is a valid phone number
const attr = isAValidPhoneNumber(to) ? 'client' : 'number';
const dial = twiml.dial({
callerId: to,
});
dial[attr]({}, 'jesus');
console.log(twiml.toString())
return twiml.toString();
};
See the 'jesus' client tag I have put ? Here is the token generator side :
exports.tokenGenerator = function tokenGenerator() {
const identity = 'jesus';
const capability = new ClientCapability({
accountSid: config.accountSid,
authToken: config.authToken,
});
capability.addScope(new ClientCapability.IncomingClientScope(identity));
capability.addScope(new ClientCapability.OutgoingClientScope({
applicationSid: config.twimlAppSid,
clientName: identity,
}));
// Include identity and token in a JSON response
return {
identity: identity,
token: capability.toJwt(),
};
};
This works for me using the node quickstart as is and changing both of these functions.
However, don't forger to change the from 'number' to 'client' in function voiceResponse because it's an incoming call and not an outgoing.
const attr = isAValidPhoneNumber(to) ? 'client' : 'number';
instead of
const attr = isAValidPhoneNumber(to) ? 'number' : 'client';
Since the default nameGenerator from the client-quickstart-node from Twilio generates a random name, it isn't properly set when receiving incoming call.

Use nlapiRequestURL to make a request to a Service

How do you use nlapiRequestURL to make a request to a service? My attempt below is failing with the error: UNEXPECTED_ERROR (output from NetSuites script execution log).
My service is set to run without login and works correctly when I directly access it through a browser using its url. Its just the request through nlapiRequestURL thats failing.
Any idea what could be going wrong?
// This code executes in Account.Model.js (register function)
// I am using my own netsuite user credential here
var cred = {
email: "MY_NETSUITE_EMAIL"
, account: "EXXXXX" // My account id
, role: "3" // Administrator
, password: "MY_NETSUITE_PASSWORD"
};
var headers = {"User-Agent-x": "SuiteScript-Call",
"Authorization": "NLAuth nlauth_account=" + cred.account + ", nlauth_email=" + cred.email +
", nlauth_signature= " + cred.password + ", nlauth_role=" + cred.role,
"Content-Type": "application/json"};
var payload = {
type: 'is_email_valid'
, email: 'spt015#foo.com'
};
// A raw request to the service works fine:
// http://mywebsite.com/services/foo.ss?type=is_email_valid&email=spt015#foo.com
// Error occurs on next line
var response = nlapiRequestURL(url, payload, headers);
You are attempting to call a non-Netsuite url with Netsuite authentication headers. You do not need that unless for some reason of your own you have implemented NS-style authorization on your service.
nlapiRequestURL does not automatically format a payload into a query string. If your service takes a posted JSON body then you need to call JSON.stringify(payload) e.g
var response = nlapiRequestURL(url, JSON.stringify(payload), headers);
If your service needs a query string like in your example then you need to construct a query string and append it to your service url. e.g.
var qs = '';
for(var k in payload) qs += k +'='+ uriEncodeComponent(payload[k]) +'&';
var response = nlapRequestURL(url +'?'+ qs.slice(0,-1), null, headers);
I would suggest changing your nlapiRequestURL to a GET instead of POST, and add the parameters to the url instead. Your function call will look like this instead.
nlapiRequestURL(url, null, headers, "GET")

agent.set.context() not working Dialogflow fulfillment

We are facing issues while managing multiple user interaction at the same time in Dialogflow.
How we can manage user unique session as we are using custom event which will process our 3rd party API and then return a response to the specific user.
To manage User unique session We try Dailogflow Set/Get Context method, to set Context with Unique Id (using this id will store API response to the Redis server) from the first intent when a user makes a first request then will traverse that Unique Id through the custom event.
Will get that Unique Id from Context and grab data from Redis server which we stored in first intent.
We used agent.set.context() to set unique id but this method is not working with "dialogflow-fulfillment"  version ^0.5.0, To get this work we have updated the version with "^0.6.1". But this will provide other error like "UnhandledPromiseRejectionWarning: Error: No responses defined for platform: null".
Required Output: Context set with a unique id and will get a proper response.
Current Output: UnhandledPromiseRejectionWarning: Error: No responses defined for platform: null
async function searchFromAPI(agent){
axios.post('https://testApi.com', searchString.data, {headers: headers})
.then((resp) => {
response.data = resp;
redisClient.set(sessionId, JSON.stringify(response));
}
}).catch(error => {
response.error = true;
response.message = error.response.statusText;
redisClient.set(sessionId, JSON.stringify(response));
});
await customsleep(2000);
const sessionId = uuid.v4();
const contextData = {'name':'userSession','lifespan': 5,'parameters':{'sessionId':sessionId}};
agent.context.set(contextData);
console.log(contextData);
agent.add('We are processing your request, Could you please wait?');
agent.add(new Suggestion("Yes"));
agent.add(new Suggestion("No"));
}
// wait for 4.5sec and call custom event
async function followOne(agent){
await customsleep(4500);
agent.setFollowupEvent('followUpTwo');
}
// wait for 4.7sec then red API response from redis server and return
async function followUpTwo(agent){
await customsleep(4000);
sess = session;
//get context
const response = agent.context.get('userSession');
// get the sessionId, Get the data from redis server
agent.add(response);
}
async function PleaseWait(agent){
await customsleep(3500);
agent.setFollowupEvent('followUpOne');
}
I found the only workaround to reassign a new context object via context.set(ctx). Then it worked.
//update context to show full menu
let context = agent.context.get('user_data');
if (!context) throw "User_data context ius nod defined in PreferrenceAdd"
let context2 = new Object();
context2 = {'name': 'user_data', 'lifespan': 40,
'parameters': context.parameters};
console.log("ctx: = " + JSON.stringify(context2));
context2.parameters.new = false;
context2.parameters.refresh = true;
agent.context.set(context2);
Check this resource on how to set a new Dialogflow outgoing context
dialogflow webhook-client.
I hope this helps you?

Resources