Janis Basis Basovs
Janis Basis Basovs

Reputation: 377

Stripe webhook fails - Webhook error: 504

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.

Upvotes: 1

Views: 1402

Answers (3)

user20253107
user20253107

Reputation:

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:

  1. Store the webhook (e.g. in the database)
  2. Send your HTTP response
  3. 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.

Upvotes: 0

Eric
Eric

Reputation: 1

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.

Upvotes: 0

Janis Basis Basovs
Janis Basis Basovs

Reputation: 377

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 :)

Upvotes: -1

Related Questions