What Is Happening
My Webhook from shopify is not passing the details to my SuiteScript 2.0 Suitelet in NetSuite.
What do I want to happen
I want shopify to send the JSON object to my netsuite Suitelet so I can process the order in NetSuite.
Details
I am trying to make a connection between shopify and Netsuite using Shopify's webhooks.
I have set up a webhook as follows
The URL for my webhook is;
https://XXXXXXX-sb1.extforms.netsuite.com/app/site/hosting/scriptlet.nl?script=XXX&deploy=XX&compid=XXXXXXX_SB1&h=XXXXXXXXXXXXXXXXXXX&caller=ecommerce&key=XXXX-XXXX-XXXX-XXXX
This link calls a Suitelet which when I personally paste the link in the URL is works. However when I click "Send Test Notification" I do not see any evidencethat the Suitelet has executed. The first line of the suitelet is;
log.debug("Running");
I have changed the Webhooks URL to instead go to RequestBin and sure enough the webhook works.
WHAT HAVE I TRED
I have removed the extra query string parameters "caller" and "key"
from the URL. Does not solve the problem.
I have confirmed the Webhook works when changing the URL to RequestBin.
One frustrating limitation with public Suitelets is that they require the User-Agent header to claim to be a browser. (See for example SuiteAnswer #38695).
I had the same issue as you with a BigCommerce webhook, and what I ended up doing was proxy the webhook through a simple Google Cloud Function that modified the user agent.
const request = require('request');
exports.webhook = (req, res) => {
request.post(
{
url: process.env.NETSUITE_SUITELET_URL,
body: req.body,
json: true,
headers: {
'User-Agent': 'Mozilla/5',
Authorization: req.headers['authorization'],
},
},
function(error, response, body) {
res.send(body);
}
);
};
Related
I have an automation that takes a Webhook from a service and posts it to Slack. The webhook goes to an API Gateway URL, authorizes through a Lambda authorizer function, and then goes to the Lambda function. This process is outlined here.
The verification is in the header["authentication"] field, which is validated by the authorizer. I scoped that text in the authorizer, and it passes it to the Lambda proxy as authenticationToken. If validated, a certificate is created and the webhook is passed to Lambda. This has all been working for a few months.
This year, as many companies tend to do, the service with the webhook is deprecating this auth method, and changing their authentication to a "Challenge-Response Check" style. So, the webhook sends a validation webhook, and the authorizer needs to make a hashed token out of several headers, including timestamp, and then the authorizer passes that hashed token back to the webhook, before sending the real webhook payload. Here is a node/express sample of how to do this:
app.post('/webhook', (req, res) => {
var response
console.log(req.body)
console.log(req.headers)
// construct the message string
const message = `v0:${req.headers['x-zm-request-timestamp']}:${JSON.stringify(req.body)}`
const hashForVerify = crypto.createHmac('sha256', process.env.ZOOM_WEBHOOK_SECRET_TOKEN).update(message).digest('hex')
// hash the message string with your Webhook Secret Token and prepend the version semantic
const signature = `v0=${hashForVerify}`
// you validating the request came from Zoom https://marketplace.zoom.us/docs/api-reference/webhook-reference#notification-structure
if (req.headers['x-zm-signature'] === signature) {
// Zoom validating you control the webhook endpoint https://marketplace.zoom.us/docs/api-reference/webhook-reference#validate-webhook-endpoint
if(req.body.event === 'endpoint.url_validation') {
const hashForValidate = crypto.createHmac('sha256', process.env.ZOOM_WEBHOOK_SECRET_TOKEN).update(req.body.payload.plainToken).digest('hex')
response = {
message: {
plainToken: req.body.payload.plainToken,
encryptedToken: hashForValidate
},
status: 200
}
....
Is this methodology supported by the authorizer/Lambda method? The APIG authorizer would need to send several headers to the Lambda authorizer function, vs just the one. I see a new feature that is changing the authorizer Type to Request (from Token), but I was not able to pass the headers to the auth function that way, after re-creating and re-deploying the APIG.
Or, should I get rid of the authorizer all together, and just do my authentication on the actual Lambda?
Or, should I start to use Lambda's newer URL feature, and get rid of APIG all together?
What is the best workflow in this use case?
Any advice or links appreciated.
I'm implementing the payment gateway API of a local company that provides a HTML fragment that renders a button in my page to handle the checkout and payment in a modal-like interface that is out of my control (without redirecting the user outside of my page).
The fragment is a script tag like this:
<form action="http://localhost:3000/api/checkout" method="POST">
<script
src="https://www.mercadopago.com.ar/integrations/v1/web-tokenize-checkout.js"
data-public-key="ENV_PUBLIC_KEY"
data-transaction-amount="100.00">
</script>
</form>
After the Card and payment info is entered by the user, the gateway makes a POST request to my API endpoint 'checkout' in Next.js (specified in the action attribute of the form) with some data to confirm the transaction as shown in the following.
/pages/api/checkout.js:
export default (req, res) => {
mercadopago.configurations.setAccessToken(configAccess_token);
var payment_data = {
transaction_amount: 100,
token: token,
description: 'Blue shirt',
installments: installments,
payment_method_id: payment_method_id,
issuer_id: issuer_id,
payer: {
email: 'john#yourdomain.com'
}
};
// Save and process the payment:
mercadopago.payment.save(payment_data).then((data) => {
Console.log(data.status);
}).catch( (error) => {
console.error(error)
});
};
I receive the payment info and I'm able to process the payment, however, the page keeps waiting for a promise-like response (stays loading indefinitely) after making the POST request. How could I handle this promise/response if I'm not explicitly making the POST request? I don't have access to the code that makes the POST request as it comes from the payment gateway itself.
I tried returning a response from my API endpoint with some data like this...
res.statusCode = 200;
res.setHeader('Content-Type', 'application/json');
res.json({success: 'success!'});
... but it just renders the JSON data as plain text after redirecting me to my API URL: "http://localhost:3000/api/checkout".
The API documentation doesn't offer any info about this matter as far as I know after hours researching it.
My goal is to display some kind of notification of success (like a toast for example) or find any other way of letting the user know the transaction was completed without creating a generic '/success' page that anyone could visit at any time.
I have little experience in backend development so any help to put me in the right direction to handle this promise/response would be highly appreciated.
What combination of requests and responses are needed to get an Oauth token from eBay? What is a runame and what headers do I need to keep eBay happy?
After three frustrating days of trying to get Ebay's oauth to give me an access token, I have finally worked it out. As the docs are pain and there is little to no help online, I have decided to post my solution here in the hope that it will help others. I am no good at StackOverflow so let me know if I need to improve my formatting.
app.get("/login/ebay", (req, res) => {
res.redirect(`https://auth.sandbox.ebay.com/oauth2/authorize?client_id=DeanSchm-TestApp-SBX-b843acc90-fd663cbb&redirect_uri=Dean_Schmid-DeanSchm-TestAp-kqmgc&response_type=code`
);
});
The first thing you need to do is redirect to this URL.
The format is like this
https://auth.sandbox.ebay.com/oauth2/authorize?client_id=&redirect_uri=&response_type=code
There is also a scope property, but I don't understand that yet, and I got back a token without is so me.
That URL takes you to the eBay login page. If you are using the sandbox, you need to create a sandbox user and login with sandbox credentials.
Once you log in, eBay will redirect you to a URL of your choosing. You enter the URL you want to be redirected to here.
It's in the ebay developer section under Get A Token From Ebay Via your Application.
This URL can be anything. you just have to handle it in node or express or whatever, because as soon as someone signs in that URL is where they are heading.
Here is how I handled it
app.get("/auth/ebay/callback", (req, res) => {
axios("https://api.sandbox.ebay.com/identity/v1/oauth2/token", {
method: "post",
headers: {
"Content-Type": "application/x-www-form-urlencoded",
Authorization:
"Basic " +
btoa(
`client public key:client secret keys`
)
},
data: qs.stringify({
grant_type: "authorization_code",
// parsed from redirect URI after returning from eBay,
code: req.query.code,
// this is set in your dev account, also called RuName
redirect_uri: "Dean_Schmid-DeanSchm-TestAp-kqmgc"
})
})
.then(response => console.log(response))
.catch(err => console.log(err));
});
A few gotchas that got me.
Make sure you have space after "Basic " in the authorisation
header.
bota is a 3rd party library that base 64 encodes your public and
secret keys. There are many ways to do this. I just did it this way because I stole a bunch of code.
With Axios, the request body is called data but with fetch and other
methods it might be called something else like body or param
The Axios method is in a get request because of the redirect from ebay
defaults to an http get.
ebay now uses https. Make sure you are using
sandbox URLs
We also had to use JS for the eBay API and solved your mention problem with developing a new Lib. It's available here. This lib will also automatically try to refresh the token if it's expires.
This is how we obtain the oAuth token:
import eBayApi from 'ebay-api';
const eBay = new eBayApi({
appId: '-- or Client ID --',
certId: '-- or Client Secret',
sandbox: false,
siteId: eBayApi.SiteId.EBAY_US,
ruName: '-- eBay Redirect URL name --' //in this case: Dean_Schmid-DeanSchm-TestAp-kqmgc
});
// This will generate the URL you need to visit
const url = eBay.oAuth2.generateAuthUrl();
// After grant access, eBay will redirect you to RuName page and set the ?code query.
// Grab the ?code and get the token with:
eBay.oAuth2.getToken(code).then((token) => {
console.log('Token', token);
ebay.oAuth2.setCredentials(token);
// Now you can make request to eBay API:
eBay.buy.browse.getItem('v1|382282567190|651094235351')
.then(item => {
console.log(JSON.stringify(item, null, 2));
})
.catch(e => {
console.log(e);
});
});
Another example with scope can we found here.
Some hints:
with "scope" you tell eBay what you plan to use. You can find the
Descriptions here, under Sandbox/Production Keys Box. (OAuth
Scopes)
if you use axios you can use the auth config, so you dont't
need btoa:
axios("https://api.sandbox.ebay.com/identity/v1/oauth2/token", {
// ...
auth: {
username: 'appId',
password: 'certId'
}
});
To use sandbox without https, e.g. localhost, you can setup a redirect on a https site and redirec/pass the code to non-https site.
I am currently trying to send a PushNotification to a Device Group using FCM with the help of Firebase Cloud Functions but once the notification is sent, it returns with code 200 but with failure :
SUCCESS response= {
multicast_id: 8834986220110966000,
success: 0,
failure: 1,
canonical_ids: 0,
results: [ { error: 'InvalidRegistration' } ]
}
Here is the code I am using to send this notification... what am I missing?
const options = {
method: 'POST',
uri: 'https://fcm.googleapis.com/fcm/send',
headers: {
'Authorization': 'key=' + serverKey,
},
body: {
to: groupId,
data: {
subject: message
},
notification: {
title: title,
body: body,
badge: 1,
},
content_available: true
},
json: true
};
return rqstProm(options)
.then((parsedBody) => {
console.log('SUCCESS response=', parsedBody);
})
.catch((err) => {
console.log('FAILED err=', err);
});
Where JSON values title, body, subject, message are String
In my case, I was sending notifications to topic ("topics/my-topic"). I was missing prepending / in the starting of topic so I was getting the same issue. SO topic should be /topics/my-topic.
May be this helps!!
There is an easier way to send a message to a device group from a Cloud Function. Use admin.messaging().sendToDeviceGroup(). Sample code and instructions are in this guide.
I think your current method is failing because there is something wrong with the group notification key provided in groupId. It should be the string key value that was returned when you created the device group. The error codes are listed in this table. For 200/InvalidRegistration it says:
Check the format of the registration token you pass to the server.
Make sure it matches the registration token the client app receives
from registering with Firebase Notifications. Do not truncate or add
additional characters.
I was losing my mind with this InvalidRegistration error.
Eventually the problem was that I was subscribing my device to "example" but sending the notification json to: "example".
But we actually need to send to "/topics/example"
2 hours of my life wasted..
A registration token is tied to a certain group of senders. When a client app registers for FCM, it must specify which senders are allowed to send messages. You should use one of those sender IDs when sending messages to the client app.
Al you need to do is add a http header 'project_id' with your sender id.
I was getting InvalidRegistration:
Basic meaning: you are using the wrong token. Why? This may happen when you a new registrationToken is given to you in onNewToken (docs), but for some reason you are using the old token. That could happen when:
You're using a different push notification library which remembers token (stores it somewhere locally) and you didn't update that library with the new token.
Your application (or other library dependencies) implements another FirebaseMessagingService, and they conflict. Only one service can accept (react to) to the action sent by the FirebaseMessaging Android library's when a new token is given to it. You can double check this by opening the AndroidManifest.xml in Android Studio and selecting the Merged Manifest tab at the bottom of the tab. You can also place debuggers in each Service from each library you use. You'll see that only one service's onNewToken gets called.
When they conflict, one doesn't get the correct token, and the FCM registration token that gets registered would be wrong. Sending a message to a wrong registration, gets you InvalidRegistration.
for me, it was a mistake that I was passing an Id from my models instead of the tokens of the users
InvalidRegistration simply means that the token is either invalid or expired. You can uninstall the app and then reinstall and get a new token and then try with that token. This will definitely solve your problem.
You can read more here.
I am setting up Stripe Connect as explained here (Standalone Account). I handle the authorization and the retrieval of the access_token on my node server.
The user can visit the link MY_SERVER_URI/authorize and will be redirected to a pre-defined stripe AUTHORIZE_URI:
app.get("/authorize", function(req, res) {
// Redirect to Stripe /oauth/authorize endpoint
res.redirect(AUTHORIZE_URI + "?" + qs.stringify({
response_type: "code",
scope: "read_write",
client_id: CLIENT_ID
}));
});
After the user authorizes Stripe Connect, he or she will be redirected to a pre-defined REDIRECT_URI, which in this case equals to MY_SERVER_URI/oauth/callback, where the following script is executed:
app.get("/oauth/callback", function(req, res) {
var code = req.query.code;
// Make /oauth/token endpoint POST request
request.post({
url: TOKEN_URI,
form: {
grant_type: "authorization_code",
client_id: CLIENT_ID,
code: code,
client_secret: API_KEY
}
}, function(err, r, body) {
var accessToken = JSON.parse(body).access_token;
// Do something with your accessToken
// For demo"s sake, output in response:
res.send({ "Your Token": accessToken });
});
});
Now everything here works fine and the application is able to get the accessToken. However, this accessToken needs to be saved and matched with the user who is granting the access from the client side.
My question therefore boils down to, how can I either pass a client-side parameter (like the client-side userId) in the oauth/callback GET request, or process the server handling on the client side (e.g. a $http GET request instead of visiting the uri)? I guess that the later is not the recommended option.
I made two attempts:
I tried to pass a parameter using a dynamic REDIRECT_URI, but the
problem is that Stripe requires that alle urls need to be specified
first (resulting that no parameters can be passed in the redirect
url).
I tried to access the MY_STRIPE_URI/authorize with a $http GET request, but this gave me the obvious error No 'Access-Control-Allow-Origin' header is present on the requested resource
What can be done?
You have to pass your user id as "state" parameter and Stripe will return it on the callback. The only way I found to avoid session
Generally your scenario is as follows:
Make request to some route on your server and store the user's id there: req.session.user = {id: '...'}
From that route redirect the user to the third party authorization URL
In the route where you receive the access token, store it in the session as well: req.session.user.access_token = '...'
Use that access token for subsequent requests to the Stripe's API
Note:
Don't try to hack the authorization_code OAuth flow
You may find Grant easier to use for that type of OAuth flow, Stripe is supported
Relevant comment