Stripe webhook fails and i assume its because Stripe doesnt recieve 'success' response from my webhook api endpoint.
Error is:
Test webhook error: 504
An error occurred with your deployment
FUNCTION_INVOCATION_TIMEOUT
I use Nextjs and its build in pages/api/createOrder folder structure to create a api. This is how my createOrder webhook looks like:
import { buffer } from "micro"
const AWS = require("aws-sdk")
AWS.config.update({
accessKeyId: process.env.MY_AWS_ACCESS_KEY_ID,
secretAccessKey: process.env.MY_AWS_SECRET_ACCESS_KEY,
region: process.env.MY_AWS_REGION,
endpoint: process.env.MY_AWS_ENDPOINT,
})
// Establish Stripe connection
const stripe = require("stripe")(process.env.STRIPE_SECRET_KEY)
const endpointSecret = process.env.STRIPE_CREATE_ORDER_SIGNING_SECRET
const createOrder = async session => {
console.log("create order - session.id: ", session.id)
console.log(
"create order - session.metadata.userID: ",
session.metadata.userID
)
console.log(
"create order - session.amount_total / 100: ",
session.amount_total / 100
)
const docClient = new AWS.DynamoDB.DocumentClient()
let date = new Date()
// create Order
let orderParams = {
TableName: process.env.ORDER_TABLE_NAME,
Item: {
id: session.id,
userID: session.metadata.userID,
amount: session.amount_total / 100,
adID: session.metadata.adID,
createdAt: date.toISOString(),
updatedAt: date.toISOString(),
},
}
docClient.put(orderParams, function (err, data) {
if (err) {
console.log("Order put err - " + JSON.stringify(err, null, 2))
} else {
console.log("Order put Success - " + JSON.stringify(data, null, 2))
}
})
// create - TTL
const ninetyDays = 1000 * 60 * 60 * 24 * 90
const currTime = Date.now()
const ttlSeconds = Math.ceil((ninetyDays + currTime) / 1000)
// update Ad
let adParams = {
TableName: process.env.AD_TABLE_NAME,
Key: { id: session.metadata.adID },
UpdateExpression: "set paid = :paid, expdate = :expdate",
ExpressionAttributeValues: {
":paid": true,
":expdate": ttlSeconds,
},
ReturnValues: "UPDATED_NEW",
}
docClient.update(adParams, function (err, data) {
if (err) {
console.log("UPDATE Ad err - " + JSON.stringify(err, null, 2))
} else {
console.log("UPDATE Ad Success - " + JSON.stringify(data, null, 2))
}
})
}
export default async (req, res) => {
if (req.method === "POST") {
const requestBuffer = await buffer(req)
const payload = requestBuffer.toString()
const sig = req.headers["stripe-signature"]
let event
// Verify that the EVENT posted came from stripe
try {
event = stripe.webhooks.constructEvent(payload, sig, endpointSecret)
} catch (err) {
console.log("ERROR", err.message)
return res.status(400).send(`Webhook create Order error: ${err.message}`)
}
// Handle the checkout.session.completed event
if (event.type === "checkout.session.completed") {
const session = event.data.object
// Fulfill update Ad -> paid = true and ttl - expdate
return createOrder(session)
.then(() => res.status(200))
.catch(err =>
res.status(400).send(`Create Order Error - ${err.message}`)
)
}
}
// Notify Stripe that req reached api
res.status(200).json({ received: true })
}
export const config = {
api: {
bodyParser: false,
externalResolver: true,
},
}
I found few solutions how to tell Stripe that the webhook call was recieved but none of them worked. Right now im useing:
res.status(200).json({ received: true })
Maybe the problem is located somewhere else?
I want to note that Order is created and Ad is updated - so the webhook works as expected except that it fails.
In an event-driven architecture (webhooks), you usually want to return a 200 as fast as possible to the provider and then process the event after. This requires a queue to decouple.
You can check out https://hookdeck.com/. Their product seems to do exactly that (returning 200 to Stripe under 200ms) so you don't get Timeout errors.
It looks like the FUNCTION_INVOCATION_TIMEOUT error is specific to Vercel, so is your app hosted there? Apparently the timeouts are quite low, and you could try increasing them.
Typically what you'd want to do when receiving a webhook is:
Store the webhook (e.g. in the database)
Send your HTTP response
Actually handle the webhook (e.g. in a background job)
This makes the response time faster and might solve your problem. It sounds like your setTimeout solution is kind of doing this — it's maybe sending the response before the handler is finished, which will be faster than putting it at the end of the handler.
After 2 month of debugging i found a solution.
I think the problem was that AWS seems to be SLOW. I added setTimeout for notifying Stripe that connection was successfull and it now works!
setTimeout(() => {
// 3. Notify Stripe that event recieved.
res.json({ received: true })
}, 2000)
If some1 has better solution plz tell us :)
Related
It is my first time trying the Aws dynamoDB and Lambda node.js. I followed exact instructions on the aws documentation here: https://docs.aws.amazon.com/apigateway/latest/developerguide/http-api-dynamo-db.html#http-api-dynamo-db-attach-integrations
Here’s the function code I used, as gotten from step 10 of the documentation
const AWS = require("aws-sdk");
const dynamo = new AWS.DynamoDB.DocumentClient();
exports.handler = async (event, context) => {
let body;
let statusCode = 200;
const headers = {
"Content-Type": "application/json"
};
try {
switch (event.routeKey) {
case "DELETE /items/{id}":
await dynamo
.delete({
TableName: "http-crud-tutorial-items",
Key: {
id: event.pathParameters.id
}
})
.promise();
body = `Deleted item ${event.pathParameters.id}`;
break;
case "GET /items/{id}":
body = await dynamo
.get({
TableName: "http-crud-tutorial-items",
Key: {
id: event.pathParameters.id
}
})
.promise();
break;
case "GET /items":
body = await dynamo.scan({ TableName: "http-crud-tutorial-items" }).promise();
break;
case "PUT /items":
let requestJSON = JSON.parse(event.body);
await dynamo
.put({
TableName: "http-crud-tutorial-items",
Item: {
id: requestJSON.id,
price: requestJSON.price,
name: requestJSON.name
}
})
.promise();
body = `Put item ${requestJSON.id}`;
break;
default:
throw new Error(`Unsupported route: "${event.routeKey}"`);
}
} catch (err) {
statusCode = 400;
body = err.message;
} finally {
body = JSON.stringify(body);
}
return {
statusCode,
body,
headers
};
};
But each time I try the API’s as it is structured on the documentation, I get ‘internal server error’. I have tried it on both terminal, and from postman, same error. I couldn’t get any helpful links elsewhere to solve this. Is there something I need to do different?
There are many reasons a Lambda would return a 500 exception to APIGW, for example:
https://aws.amazon.com/premiumsupport/knowledge-center/api-gateway-lambda-stage-variable-500/
https://aws.amazon.com/premiumsupport/knowledge-center/api-gateway-internal-server-error/
My suggestion is to strip back your function code, and only return a response to APIGW with no other logic and test that. Then you can decide to work forwards or backwards from there.
I'm at a loss. I have a svelte application (Backend: Express, Frontend: Axios). I have a MongoDB with locations. Locations have an array of bands. And I want to add bands to this array. The backend seems to work fine, at least with Postman it works. But when I try to add a band through the frontend, I get a http 500 error.
This is the back end code:
app.put('/api/locations/:location', async (req, res) => {
let location = req.params.location;
let updatedlocation = req.body;
try {
await client.connect();
const database = client.db('Concerts');
const collection = database.collection('locations');
const query = { locationname: location };
const result = await collection.updateOne(query, { $set: updatedlocation });
if (result.matchedCount === 0) {
let responseBody = {
status: "No Location under the name " + location
}
res.status(404).send(responseBody);
}
else {
res.send({ status: "Location " + location + " has been updated." });
}
} catch (error) {
res.status(500).send({ error: error.message });
}})
This is the method in the frontend:
function addConcert() {
location.concerts.push(chosenband);
console.log("http://localhost:3001/api/locations/" +name);
console.log(location);
axios.put("http://localhost:3001/api/locations/" +name, location)
.then((response) => {
alert("Konzert wurde hinzugefügt");
})
.catch((error) => {
alert("Nope");
console.log(error);
});
}
Info: the {chosenband} comes from a select. This seems to work as well, as the console logs show.
The object is correct and includes the new band:
this is the log from the browser
So the object seems fine. Also the put-url is correct.
But I always get this 500 error
Thankful for any advise!
Found the problem. I didn't instantiate the location correctly. First I instantiated it simply as
let location = {}
, that didn't work. When I instantiated it with all the attributes
let location = {example:"", second:""}
it worked. Thanks for your help
I'm trying to build a scraping script to get a bunch of Discord server's total members. I actually did that with Puppeteer like below but I think my IP address has been banned because I'm getting "Invite Invalid" error from Discord even though invite links are working.
My question is that does Discord have APIs to get any server's total member count? Or is there any 3rd party library for that purpose? Or any other method?
const puppeteer = require('puppeteer')
const discordMembers = async ({ server, browser }) => {
if (!server) return
let totalMembers
const page = await browser.newPage()
try {
await page.goto(`https://discord.com/invite/${server}`, {
timeout: 3000
})
const selector = '.pill-qMtBTq'
await page.waitForSelector(selector, {
timeout: 3000
})
const totalMembersContent = await page.evaluate(selector => {
return document.querySelectorAll(selector)[1].textContent
}, selector)
if (totalMembersContent) {
totalMembers = totalMembersContent
.replace(/ Members/, '')
.replace(/,/g, '')
totalMembers = parseInt(totalMembers)
}
} catch (err) {
console.log(err.message)
}
await page.close()
if (totalMembers) return totalMembers
}
const asyncForEach = async (array, callback) => {
for (let i = 0; i < array.length; i++) {
await callback(array[i], i, array)
}
}
const run = async () => {
const browser = await puppeteer.launch({
headless: true,
args: ['--no-sandbox']
})
const servers = ['tQp4pSE', '3P5K3dzgdB']
await asyncForEach(servers, async server => {
const members = await discordMembers({ server, browser })
console.log({ server, members })
// result
// { server: 'tQp4pSE', members: 57600 }
// { server: '3P5K3dzgdB', members: 159106 }
})
await browser.close()
}
run()
Update: Mar 22, 2022
Thanks for #Vaviloff's answer we can actually access Discord's private APIs but the problem is it's only accessible over browser. I'm getting Request failed with status code 400 issue from Axios. Is it a CORS issue? How do we get the results in a Node.js app?
const axios = require('axios')
const discordMembers = async ({ server }) => {
try {
const apiResult = await axios({
data: {},
method: 'get',
url: `https://discord.com/api/v9/invites/${server}?with_counts=true&with_expiration=true`
})
console.log(apiResult)
} catch (err) {
console.log(err)
}
}
discordMembers({ server: 'tQp4pSE' })
A lot of modern web applications have their own internal APIs. Oftentimes you can spot frontend making requests to it, by using Networking tab in Devtools (filter by Fetch/XHR type):
Such API endpoints can change any time of course, but usually the last for a long time and is a rather convenient way of scraping
Currently Discord uses this URL for basic instance description:
https://discord.com/api/v9/invites/tQp4pSE?with_counts=true&with_expiration=true
By accessing it you get the desired data:
Update
To make your code work don't send any data in the request:
const apiResult = await axios({
method: 'get',
url: `https://discord.com/api/v9/invites/${server}?with_counts=true&with_expiration=true`
})
API
(https://api.mosmarts.com/truck/v0/api.php)
The API is scripted in PHP and accepts GET & POST commands and in return it responds back with a JSON response data.
To retrieve data the API requires “functionality” and “action” among other params as show below.
Command for retrieving all truck
Command for retrieving all truck
Payloads
{
"functionality" : "get",
"action" : "get_all_truck"
}
Command to retrieving truck inspection details by id
Payloads
{
"functionality" : "get",
"action" : "get_inspection_history",
"truckId" : "1"
}
NB: you will get truckId from command "get_all_truck" above
What’s expected from you
As the software developer you are tasked to design and develop a web-based backend solution that will have:
Dashboard: -
• Retrieve and indicate total number of trucks
• Retrieve and indicate number of inspection repairs requested 2. List all Trucks: -
• Implement search option
Inspection List: -
• Implement filter by truck
i have some code using express.js bt i get is a 404 error, no data retrieved.
app.js
const apiCallFromRequest = require('./Request')
const apiCallFromNode = require('./NodeJsCall')
const http = require('http')
http.createServer((req, res) => {
if(req.url === "/request"){
apiCallFromRequest.callApi(function(response){
//console.log(JSON.stringify(response));
res.write(JSON.stringify(response));
res.end();
});
}
else if(req.url === "/node"){
apiCallFromNode.callApi(function(response){
res.write(response);
res.end();
});
}
// res.end();
}).listen(3000);
console.log("service running on 3000 port....");
NodeJsCall.js
const https = require('https');
_EXTERNAL_URL = 'https://api.mosmarts.com/truck/v0/api.php';
const callExternalApiUsingHttp = (callback) => {
https.get(_EXTERNAL_URL, (resp) => {
let data = '';
// A chunk of data has been recieved.
resp.on('data', (chunk) => {
data += chunk;
});
// The whole response has been received. Print out the result.
resp.on('end', () => {
return callback(data);
// console.log(JSON.stringify(data));
});
}).on("error", (err) => {
console.log("Error: " + err.message);
});
}
module.exports.callApi = callExternalApiUsingHttp;
Request.js
const request = require('request');
_EXTERNAL_URL = 'https://api.mosmarts.com/truck/v0/api.php';
const callExternalApiUsingRequest = (callback) => {
request(_EXTERNAL_URL, { json: true }, (err, res, body) => {
if (err) {
return callback(err);
}
return callback(body);
});
}
module.exports.callApi = callExternalApiUsingRequest;
Hey Gerald you can find a simple response for this kind of question on google.
if you are a real beginner I would propose you the Axios npm.
here is an example of a really simple GET request with axios.
axios.get('https://api.github.com/users/mapbox')
.then(response => {
console.log(response.data.created_at);
});
I'm trying to create a service that will be a middle man between my frontend framework and shutterstock. I'm running into an issue when trying to license an image where it says my subscription is unusable or cannot be found. I have done exactly what the documentation said and I don't know what I am missing.
let sstk = require("shutterstock-api");
sstk.setSandbox(true);
sstk.setAccessToken(process.env.SHUTTERSTOCK_TOKEN);
// Instantiate the shutterstock images api
const imagesApi = new sstk.ImagesApi();
// Instantiate the shutterstock users api
const usersApi = new sstk.UsersApi();
// Creates the body to send to shutterstock
const body = {
images: imageIds.map((imageId) => {
return {
image_id: imageId,
price: 0,
metadata: {
customer_id: "0",
},
};
}),
};
// Get subscription so we can grab the subscription id
usersApi
.getUserSubsciptionList()
.then(({ data }) => {
const subscription_id = data[0].id;
const queryParams = {
format: "jpg",
size: "huge",
subscription_id,
};
// If we successfully get the subscription id then license the images
imagesApi
.licenseImages(body, queryParams)
.then(({ data }) => {
console.log("licensedImages", data);
// Check if there was an error on any of the images
let numOfErrors = 0;
data.forEach((image) => {
if (image.error) {
numOfErrors += 1;
}
});
// If some of the images were successful
if (numOfErrors > 0 && numOfErrors < data.length) {
return errorHandler
// If all the images failed
} else if (numOfErrors > 0) {
return errorHandler
}
// If there are no errors send back the data to the frontend to manipulate it how it needs
return res.status(200).send(data);
})
.catch((err) => {
// If license error wasn't handled by Shutterstock
console.error(err);
return errorHandler
});
})
.catch((error) => {
// If subscription error wasn't handled by Shutterstock
console.error(error);
return errorHandler
});
Logged Response with status code 200
licensedImages [ exports {
image_id: id,
error:
'ValidationError: Subscription is unusable or cannot be found. License failed' } ]
I'm not sure why its not working. I've logged my subscription id and image id and they are correct.
The format and size do match the formats available on the subscription.
The subscription is a Developer Platform license.
What am I missing?
This is on an expressjs api
It looks like your Shutterstock account has both a 'Developer Platform' subscription and standard user subscription, which causes issues in the api. Your code is correct - the problem is with the validation of your subscription within the licensing flow. We'll reach out to you via email once we correctly attribute your different subscriptions.