Raw Body in API Service request

Hi,

We are using API Services as a Node.js project. We are using it for Stripe payment service. For a certain functionality of Stripe, we need a raw body from the HTTP request that is sent to our API Service. Backendless currently does not implement a feature of getting a raw body in a method. Can this be implemented? It is for security reasons.

I already tried getting the raw body by setting one String parameter in the method:

/**
  * @route POST 
  * @param {String} rawBody
  */
    async method(rawBody) { ... }

But in that case, the request doesn’t seem to be getting through to the endpoint.

Hello @Hrvoje_Ditrih

Could you please provide a little bit more information what you are looking for?

  • why do you need this method?
  • what exactly the method should do?

we need a raw body from the HTTP request that is sent to our API Service

Isn’t the request called from your client to the Stripe API Service, is it?

Regards, Vlad

Hi,

This method is used as a Stripe webhook, so this endpoint is being called by Stripe.

For security reasons, to validate that the Stripe webhook request is valid we need a raw body of the request in order to perform some kind of checksum validation.

Stripe —webhook request----> /webhook -------> checksum validation

/**
  * @route POST /webhook
  * @param {String} rawBody
  */
    async webhook(rawBody) { 
        stripe.checkSignature(stripeWebhookSecret, rawBody, signature)
    }

Hi Hrvoje,

Could you please point me in the right direction here, I’d like to understand Stripe’s requirement to validate the webhook request. Is it described somewhere in their documentation?

Regards,
Mark

Hi Mark,

You can read up on validating webhook’s here https://stripe.com/docs/webhooks/signatures.

Regards,
Hrvoje

Hi Hrvoje,

Thank you. This will require us to introduce a new method/endpoint that will provide the entire payload. Since signature validation is optional, it should not block you. Once the new method is added, you will be able to switch to that.

Regards,
Mark

Actually, it will not work that. You started on the right path - adding a method with one parameter - that parameter will contain the entire JSON payload. Have you updated the webhook endpoint in Stripe so they call your method instead of the one from our service?

Hi,

Receiving of the payload is not a problem in the JSON form. What is needed is a raw body in a string format and that we cannot do.

Yes, it is optional, but highly insecure if not used.

We tried having one string parameter on the API service but in that case the request doesn’t even reach the endpoint for some unknown reason to me. If I put one parameter as JSON object, then the webhook request reaches the endpoint with no problem.

This works:

/**
  * @route POST /webhook
  * @param {JSON} event
  */
    async webhook(event) {

     }

This does not work, the webhook request does not reach the endpoint altogether

/**

  • @route POST /webhook
  • @param {String} rawBody
    */
    async webhook(rawBody) {
    }

Two questions for you:

  1. What if you do not specify the @param at all? What do you get then?
  2. What if you specify the {JSON} type argument and then use something like JSON.stringify ? Does it get you what you need?
  1. Then I don’t get any parameters in the method but the endpoint is reached.
  2. Tried that before. The signature check failed.

Have you tried declaring the parameter as:

 @param {Object} rawBody

Is there a possibility that there is a problem with the signature check algorithm?

My suggestion would be to have a raw body in an invocation context.
Something like: this.request.body which would be called within the API Service method.

Yes, I tried that, when I put {JSON} it is actually {Object} but I do it like that so I know what I’m expecting from the parameter.

I don’t believe so. The method is provided by stripe the object and I tried the exact same code from their documentation.

adding raw body to the request object seems like a reasonable improvement, I will discuss it with the team.

Regards,
Mark

You can try it yourself.

Setting up stripe test account takes minutes, and you can send test webhooks from their dashboard.

This is the whole code for your backend service that you need:

// Stripe keys
const STRIPE_SECRET_KEY='sk_test_...'

// Required to run webhook
const STRIPE_WEBHOOK_SECRET='whsec_...'

const stripe = require("stripe")(STRIPE_SECRET_KEY);


class StripeService {

    /**
     * @route POST /webhook
     * @param {JSON} rawBody
     */
    webhook(rawBody) {
        console.log(rawBody)
        let data
        let eventType
        
        // Retrieve the event by verifying the signature using the raw body and secret.
        let event
        let signature = this.request.headers["Stripe-Signature"]

        
        event = stripe.webhooks.constructEvent(
            rawBody,
            signature,
            STRIPE_WEBHOOK_SECRET
        )
        
        // Extract the object from the event.
        data = event.data;
        eventType = event.type;
        

        if (eventType === "checkout.session.completed") {
            console.log(`🔔  Payment received!`);
        }
    }

}

Backendless.ServerCode.addService(StripeService)

Hello,

Any news about the raw body (string format) in the InvocationContext or another solution ?

Thank you

Hello @Seb777 ,

you can send a raw body. Just create a method with string argument:

'use strict';

class /*SERVICE_NAME*/TestRaw/*SERVICE_NAME*/ {
/*TYPES*/
/*TYPES*/
/*METHODS*/
/*METHOD test*//*METHOD_COMPLEX_TYPES*//**//*METHOD_COMPLEX_TYPES*/

/**
* @route POST /test
* @param {String} [test] 
* @returns {String}
*/
async test(test) {
/*METHOD_BODY*/
  return test

/*METHOD_BODY*/
}
/*METHODS*/
}

Backendless.ServerCode.addService(/*SERVICE_NAME*/TestRaw/*SERVICE_NAME*/);

The use the following cUrl to call:

curl -X "POST" "https://<your-domain>/api/services/TestRaw/test" -d 'zxceeffffffffee'

The result will be:

HTTP/1.1 200 OK
server: nginx
date: Tue, 02 Aug 2022 12:26:19 GMT
content-type: application/json
content-length: 17
access-control-allow-origin: *
access-control-allow-methods: POST, GET, OPTIONS, PUT, DELETE, PATCH
access-control-expose-headers:
strict-transport-security: max-age=86400

"zxceeffffffffee"%

Hello @sergey.kuk

Thank you for your help.

Yes, I already tried with a string argument, but this is what I get:

{
“code”: 14004,
“message”: “Service invocation failed: Illegal unquoted character ((CTRL-CHAR, code 10)): has to be escaped using backslash to be included in string value…”
}

The request is from Stripe so I can’t do much to change the request body.

Hello @Seb777

Could you please provide the request you are sending?

Regards,
Alexander

Hello @Alexander_Pavelko

Here is what I have in the logs

“Service invocation failed: Illegal unquoted character ((CTRL-CHAR, code 10)): has to be escaped using backslash to be included in string value\n at [Source: (String)""{\n "id": "evt_1",\n "object": "event",\n "api_version": "2020-08-27",\n "created": 1659464460,\n "data": {\n "object": {\n "id": "cs_test",\n "object": "checkout.session",\n "after_expiration": null,\n "allow_promotion_codes": null,\n "amount_subtotal": 10000,\n "amount_total": 10000,\n "automatic_tax": {\n "enabled": false,\n "status": null\n },\n "billing_addres"[truncated 1908 chars]; line: 1, column: 4]”

Here is what I found in Stripe as input

{
“id”: “evt_1”,
“object”: “event”,
“api_version”: “2020-08-27”,
“created”: 1659464460,
“data”: {
“object”: {
“id”: “cs_test”,
“object”: “checkout.session”,
“after_expiration”: null,
“allow_promotion_codes”: null,
“amount_subtotal”: 10000,
“amount_total”: 10000,
“automatic_tax”: {
“enabled”: false,
“status”: null
},
“billing_address_collection”: null,
“cancel_url”: “https://eve.backendless.app/…”,
“client_reference_id”: null,
“consent”: null,
“consent_collection”: null,
“currency”: “eur”,
“customer”: “cus_1”,
“customer_creation”: null,
“customer_details”: {
“address”: {
“city”: null,
“country”: “FR”,
“line1”: null,
“line2”: null,
“postal_code”: null,
“state”: null
},
“email”: “email@gmail.com”,
“name”: “Mr Dupont”,
“phone”: null,
“tax_exempt”: “none”,
“tax_ids”: [
]
},
“customer_email”: null,
“expires_at”: 1659550828,
“livemode”: false,
“locale”: null,
“metadata”: {
},
“mode”: “subscription”,
“payment_intent”: null,
“payment_link”: null,
“payment_method_options”: null,
“payment_method_types”: [
“card”
],
“payment_status”: “paid”,
“phone_number_collection”: {
“enabled”: false
},
“recovered_from”: null,
“setup_intent”: null,
“shipping”: null,
“shipping_address_collection”: null,
“shipping_options”: [
],
“shipping_rate”: null,
“status”: “complete”,
“submit_type”: null,
“subscription”: “sub_1”,
“success_url”: “https://eve.backendless.app/…”,
“total_details”: {
“amount_discount”: 0,
“amount_shipping”: 0,
“amount_tax”: 0
},
“url”: null
}
},
“livemode”: false,
“pending_webhooks”: 1,
“request”: {
“id”: null,
“idempotency_key”: null
},
“type”: “checkout.session.completed”
}

Thank you