Stripe Webhooks and AWS-S3 function call issue

Hello @Drew_DiPalma thanks for the updates.

I had 3 problems with Realm Functions, I just tested them now, please allow me to share:

1— Stripe, couldn’t make 2 calls in a row using its client:
IT IS WORKING NOW! Thanks!

exports = async() => {
  const stripe = require('stripe')(context.values.get('StripeSecretKey'))
 
  const res = await stripe.customers.list()
  console.log(JSON.stringify(res))
  
  const res2 = await stripe.customers.list()
  console.log(JSON.stringify(res2))
  // this second call would just hang forever and fail, it does work now
}
// I'm using latest package stripe 8.216.0

2— AWS SDK v3, S3 Client, it fails just by requiring the client:
IT IS NOT WORKING YET, STILL FAILING

exports = () => {
  const { S3Client } = require('@aws-sdk/client-s3')
  // just by requiring the client, it fails
}
// using latest @aws-sdk/client-s3 3.67.0

Here is the full error:

> ran at 1649539308006
> took 
> error: 
failed to execute source for 'node_modules/@aws-sdk/client-s3/dist-cjs/index.js': FunctionError: failed to execute source for 'node_modules/@aws-sdk/client-s3/dist-cjs/S3.js': FunctionError: failed to execute source for 'node_modules/@aws-sdk/client-s3/dist-cjs/S3Client.js': FunctionError: failed to execute source for 'node_modules/@aws-sdk/client-s3/dist-cjs/runtimeConfig.js': FunctionError: failed to execute source for 'node_modules/@aws-sdk/client-sts/dist-cjs/index.js': FunctionError: failed to execute source for 'node_modules/@aws-sdk/client-sts/dist-cjs/STS.js': FunctionError: failed to execute source for 'node_modules/@aws-sdk/client-sts/dist-cjs/STSClient.js': FunctionError: failed to execute source for 'node_modules/@aws-sdk/client-sts/dist-cjs/runtimeConfig.js': FunctionError: failed to execute source for 'node_modules/@aws-sdk/credential-provider-node/dist-cjs/index.js': FunctionError: failed to execute source for 'node_modules/@aws-sdk/credential-provider-node/dist-cjs/defaultProvider.js': FunctionError: failed to execute source for 'node_modules/@aws-sdk/credential-provider-ini/dist-cjs/index.js': FunctionError: failed to execute source for 'node_modules/@aws-sdk/credential-provider-ini/dist-cjs/fromIni.js': FunctionError: failed to execute source for 'node_modules/@aws-sdk/util-credentials/dist-cjs/index.js': FunctionError: failed to execute source for 'node_modules/@aws-sdk/util-credentials/dist-cjs/parse-known-profiles.js': FunctionError: failed to execute source for 'node_modules/@aws-sdk/shared-ini-file-loader/dist-cjs/index.js': FunctionError: failed to execute source for 'node_modules/@aws-sdk/shared-ini-file-loader/dist-cjs/loadSharedConfigFiles.js': FunctionError: failed to execute source for 'node_modules/@aws-sdk/shared-ini-file-loader/dist-cjs/slurpFile.js': TypeError: Cannot access member 'readFile' of undefined
	at node_modules/@aws-sdk/shared-ini-file-loader/dist-cjs/slurpFile.js:18:16(34)

	at require (native)
	at node_modules/@aws-sdk/shared-ini-file-loader/dist-cjs/loadSharedConfigFiles.js:24:27(52)

	at require (native)
	at node_modules/@aws-sdk/shared-ini-file-loader/dist-cjs/index.js:19:30(41)

	at require (native)
	at node_modules/@aws-sdk/util-credentials/dist-cjs/parse-known-profiles.js:22:40(34)

	at require (native)
	at node_modules/@aws-sdk/util-credentials/dist-cjs/index.js:19:30(41)

	at require (native)
	at node_modules/@aws-sdk/credential-provider-ini/dist-cjs/fromIni.js:16:34(28)

	at require (native)
	at node_modules/@aws-sdk/credential-provider-ini/dist-cjs/index.js:17:30(32)

	at require (native)
	at node_modules/@aws-sdk/credential-provider-node/dist-cjs/defaultProvider.js:36:41(51)

	at require (native)
	at node_modules/@aws-sdk/credential-provider-node/dist-cjs/index.js:17:30(32)

	at require (native)
	at node_modules/@aws-sdk/client-sts/dist-cjs/runtimeConfig.js:30:42(58)

	at require (native)
	at node_modules/@aws-sdk/client-sts/dist-cjs/STSClient.js:58:31(94)

	at require (native)
	at node_modules/@aws-sdk/client-sts/dist-cjs/STS.js:54:27(90)

	at require (native)
	at node_modules/@aws-sdk/client-sts/dist-cjs/index.js:18:30(35)

	at require (native)
	at node_modules/@aws-sdk/client-s3/dist-cjs/runtimeConfig.js:26:28(48)

	at require (native)
	at node_modules/@aws-sdk/client-s3/dist-cjs/S3Client.js:66:31(114)

	at require (native)
	at node_modules/@aws-sdk/client-s3/dist-cjs/S3.js:224:26(515)

	at require (native)
	at node_modules/@aws-sdk/client-s3/dist-cjs/index.js:18:30(35)

My goal is to use AWS SDK v3, to replace the third-party services that will be deprecated later the year, so would be good if you could look into.

3— Must have access to the raw request body for Stripe Webhook:
NOT YET, THERE IS NO WAY TO ACCESS THE RAW REQUEST BODY ON WEBHOOKS

// to create the stripe event with need the raw body
// the body.text() does not work
exports = async function({ headers, body }, response) {

  const stripe = require('stripe')(context.values.get('StripeSecretKey'))

  let event

  try {
    event = stripe.webhooks.constructEvent(
      body.text(), // HERE IS THE ISSUE, WE CAN'T USE THE TEXT
      headers['Stripe-Signature'][0],
      context.values.get('StripeWebhookSecret'),
    )
  }
  catch (err) {
    // ...
  }
}

Here is the error that the Stripe library emits:

“Webhook Error: No signatures found matching the expected signature for payload. Are you passing the raw request body you received from Stripe? https://github.com/stripe/stripe-node#webhook-signing ”

For the Stripe Webhook I had to use a PHP server for that.

Wish I didn’t, I have the entire Stripe implementation runing successfully on Realm Functions, only the Webhook I have to do somewhere else.

Thank you very much.

G’Day @andrefelipe, :wave:

I have extracted this post to a new one as there are lot of different issues getting mixed up with old post and it would help to separate each issue to a new post.

About the issue on Stripe Webhooks, could you share more detail if text() isn’t populating for you, or isn’t in the format you expect?

On testing, text() is working fine on POST endpoints, but not GET endpoints.

On your aws-sdk/client-s3 question, could you please try using v2 client and share your observations? The engineering team is working on improvements for s3.

I look forward to your response.

Cheers, :performing_arts:

1 Like

Thanks @henna.s for the organization.

I will follow the same numbers:

2— AWS SDK v3, S3 Client
The v2 client works fine. I am using it through the Realm’s third-party services.

3— Must have access to the raw request body for Stripe Webhook:
I am indeed using POST. And the text() is populating correctly, it just isn’t in the format expected by Stripe, which is something like <Buffer 7b 0a 20 20 22 63 72 65 61 74 ...
For cross reference the issue is discussed here too Setup Stripe webhook - #3 by rouuuge

Thanks a lot, have a good day too.

Thanks, @andrefelipe for the updates.

There are two posts shared with me, could you check if this helps you with your use-case

  1. Webhook signature verification for stripe: Are you passing raw request body received from stripe? | by Sukhada Gholba | Medium
  2. Raw Request Body Issues on SO

I will update you on feedback from the engineering team.

Cheers, :performing_arts:

@henna.s Any updates?

I am not sure but feels like the realm is transforming the request body before passing it to the attached function. Therefore “stripe.webhooks.constructEvent” is not getting the data in the desired format.

We solved the issue by manually verifying the signature with a custom function.

/***
 * This function manually validates the stripe signature for the webhook event
 *
 * Returns: boolean => true if valid request
 *
 * stripeSignature: string => Stripe-Signature received from request header i.e. request.headers["Stripe-Signature"][0]
 * requestBody:string => request body from header i.e. request.body.text()
 */
exports = function (stripeSignature, requestBody) {
  // e.g stripeSignature = "t=2345345,v1=sdfas234sfasd, v0=akhjgfska1234234123"
  const { getUnixTime } = require("date-fns");

  //0. tolerance of 5 mins
  const TOLERANCE = 300;

  //1. get webhook endpoint secret from the values
  const STRIPE_WEBHOOK_SECRET = context.values.get("STRIPE_WEBHOOK_SECRET");
  console.log("STRIPE_WEBHOOK_SECRET: ", STRIPE_WEBHOOK_SECRET);

  // 2. split the signature using "," to get a list of signature elements
  const signatureComponents = stripeSignature.split(",");

  // 3. Create a dictionary with with prefix as key for the splitted item
  const signComponentsDict = {};
  signatureComponents.forEach((item) => {
    const [prefix, value] = item.split("=");
    if (["v1", "t"].includes(prefix)) {
      signComponentsDict[prefix] = value;
    }
  });

  // 4. crete a signed payload by concatenting "t" element and the request body
  const signedPayload = `${signComponentsDict["t"]}.${requestBody}`;

  //5. the "v1" is actual signature from stripe
  const incomingSignature = signComponentsDict["v1"];

  //6. compute your own signature using the signed payload created in step 4
  const expectedSignature = utils.crypto.hmac(
    input = signedPayload,
    secret = STRIPE_WEBHOOK_SECRET,
    hash_function = "sha256",
    output_format = "hex"
  );

  //7. Get current time in unix format and also get the time from stripeSignature
  const incomingTimestamp = signComponentsDict["t"];
  const currTimestamp = getUnixTime(new Date());
  const timestampDiff = currTimestamp - incomingTimestamp;

  //8. Comparision the signature respecting tolerance
  if (timestampDiff <= TOLERANCE) {
    if (incomingSignature === expectedSignature) {
      return true;
    }
  }

  return false;
};

3 Likes

G’ Day @Ebrima_Ieigh , @Surender_Kumar ,

Thank you @Surender_Kumar for sharing the code snippet, appreciate it.

@Ebrima_Ieigh , unfortunately, “rawBody” is not supported by the platform and perhaps text() is something you are not looking for?

Could you please help me know if it’s the same issue as what Andre is experiencing or if any of the above links or the snippet provided by Surender has helped you?

I look forward to your response.

Best,
Henna

1 Like

The stripe issue seems to be fixed now as the following is working for me

exports = async function(req, response) {
    const stripe = require('stripe')(context.values.get('STRIPE_API_KEY'));
    const { headers, body} = req;
    const endpointSecret = context.values.get('STRIPE_WEBHOOK_SECRET')

        // Get the signature sent by Stripe
        const signature = headers['Stripe-Signature'][0]

        try {
            event = stripe.webhooks.constructEvent(
                body.text(),
                signature,
                endpointSecret
            );
            console.log('body-text worked')
        } catch (err) {
            console.log(`️body-text  Webhook signature verification failed.`, err.message);
            console.error(err)

            response.setStatusCode(400);
            return response
        }
    
    response.setStatusCode(200);
    return response;
};

3 Likes

Thanks for the confirmation @clueless_dev.

I will close this topic now. If help is needed for a related issue, please create a new topic and link this topic with that.

Happy Coding!

Cheers, :performing_arts:

1 Like