#ncls-webhook-signature

1 messages ยท Page 1 of 1 (latest)

oak fiberBOT
paper plinth
#

Hi ๐Ÿ‘‹

I would recommend logging the endpoint secret and ensuring it matches the secret for the webhook endpoint

paper plinth
#

What is the ID for the registered endpoint here?

cloud veldt
idle arch
#

^ This is my friend btw

cloud veldt
#

im btw the friend of @idle arch

paper plinth
cloud veldt
#

ist currently in the Testmode

paper plinth
#

And it stopped working when you deployed your code?

idle arch
#

No, it just randomly stopped recently. While we tested, everything was fine and we didn't change anything ever since but recently a customer couldn't pay using it and when we checked, we saw that it fails to verify the signature

#

It just randomly started doing that. We don't know why

paper plinth
#

At what time?

idle arch
paper plinth
#

I can see that the live mode endpoint is disabled due to too many failed notification deliveries

#

I would recommend

  1. Double Checking the webhook secret
  2. Logging the payload
  3. Logging the $sig_header
  4. Testing with the json_decode approach we list here: https://stripe.com/docs/webhooks#example-endpoint
  5. Checking with your hosting provider to ensure they didn't start modifying the incoming POST request body.
idle arch
paper plinth
#

I can tell you that Stripe did not change anything about how Stripe signatures are constructed or the payloads of webhook events.

#

So I would suggest a closer look at your code

idle arch
#

I tried generating the signature myself and they don't match unless I did it wrong. I did the following:

  • Getting the timestamp from the request header, getting the payload and combining it with a dot so that the string looks something like this:
    1696263552.{"key": "value"} (Is the space the issue? Shouldn't be, right?)
  • Then I encrypted that string using SHA-256 with our webhook secret as a key.
    I don't know if I can share the signature from Stripe and what I got doing it manually due to security reasons but they are not the same and I don't know why.
#

Since I use the official Stripe PHP Library to do all the logic and compare them, it should work, right?

paper plinth
#

The Stripe library includes a tolerance parameter for calculating the timestamp.

#

Any whitespace could be the issue here as well

#

Since the payload must match exactly what we expect

idle arch
#

I mean, if Stripe sends their payload with whitespace, which seems to be the case (please correct me if I'm wrong), then whitespace can't be the issue.

paper plinth
idle arch
#

This is the part that's causing our error:

// Check if expected signature is found in list of signatures from
// header
$signedPayload = "{$timestamp}.{$payload}";
$expectedSignature = self::computeSignature($signedPayload, $secret);
$signatureFound = false;
foreach ($signatures as $signature) {
  if (Util\Util::secureCompare($expectedSignature, $signature)) {
    $signatureFound = true;

    break;
  }
}
if (!$signatureFound) {
  throw Exception\SignatureVerificationException::factory(
    'No signatures found matching the expected signature for payload',
    $payload,
    $header
  );
}
paper plinth
#

Yes and it hasn't changed in over 3 years

idle arch
#

But what can cause this then? The secret is correct, the header and payload look fine too. Unless Stripe sends the wrong timestamp, uses another payload format while generating the signature or is using the wrong secret, there's nothing coming to my mind that can cause this behavior

paper plinth
#

If stripe were sending the wrong timestamp then all webhook events would be failing to deliver and that would definitely be a big deal.

idle arch
#

Yes, I know. I'm absolutely clueless...

#

It makes zero sense. Triple checked everything

paper plinth
#

And this is for the LIve or the Test mode endpoints?

oak fiberBOT
wide sandal
#

ncls-webhook-signature

#

@idle arch This is almost always a bug on your end. Either using the wrong secret even though you think you use the right one, or having something in your stack that "modifies" the JSON payload we send. Anything in your system that changes that JSON is going to cause issue.
Unfortunately this can be quite hard to debug but my recommendation is to add clear logs to your own code to dump the content of the payload and the secret to a file for example and confirm they look as expected

idle arch
# wide sandal <@602953499407286331> This is almost always a bug on your end. Either using the ...

I just quadruple-checked the webhook secret again by copying it from Stripe and using CTRL + F in my logs. Looked fine. No whitespaces in front or back either.
Also logged the payload I get from Stripe. Format looks fine to me, but maybe you can spot somehing that's off since you see these way more often than me (I removed all IDs and personal info):

{
  "id": "<hidden>",
  "object": "event",
  "api_version": "2022-11-15",
  "created": 1696263552,
  "data": {
    "object": {
      "id": "<hidden>",
      "object": "checkout.session",
      "after_expiration": null,
      "allow_promotion_codes": null,
      "amount_subtotal": 500,
      "amount_total": 500,
      "automatic_tax": {
        "enabled": false,
        "status": null
      },
      "billing_address_collection": null,
      "cancel_url": null,
      "client_reference_id": null,
      "consent": null,
      "consent_collection": null,
      "created": 1696261752,
      "currency": "eur",
      "currency_conversion": null,
      "custom_fields": [

      ],
      "custom_text": {
        "shipping_address": null,
        "submit": null,
        "terms_of_service_acceptance": null
      },
      "customer": null,
      "customer_creation": "if_required",
      "customer_details": {
        "address": {
          "city": null,
          "country": "AT",
          "line1": null,
          "line2": null,
          "postal_code": null,
          "state": null
        },
        "email": "<hidden>",
        "name": "<hidden>",
        "phone": null,
        "tax_exempt": "none",
        "tax_ids": [

        ]
      },
      "customer_email": null,
      "expires_at": 1696348152,
      "invoice": null,
      "invoice_creation": {
        "enabled": false,
        "invoice_data": {
          "account_tax_ids": null,
          "custom_fields": null,
          "description": null,
          "footer": null,
          "metadata": {
          },
          "rendering_options": null
        }
      },
      "livemode": false,
      "locale": null,
      "metadata": {
        "invoice_id": "8859"
      },
      "mode": "payment",
      "payment_intent": "<hidden>",
      "payment_link": null,
      "payment_method_collection": "if_required",
      "payment_method_configuration_details": {
        "id": "<hidden>",
        "parent": null
      },
      "payment_method_options": {
      },
      "payment_method_types": [
        "card",
        "bancontact",
        "eps",
        "giropay",
        "ideal",
        "p24",
        "sofort",
        "alipay",
        "klarna",
        "link"
      ],
      "payment_status": "paid",
      "phone_number_collection": {
        "enabled": false
      },
      "recovered_from": null,
      "setup_intent": null,
      "shipping_address_collection": null,
      "shipping_cost": null,
      "shipping_details": null,
      "shipping_options": [

      ],
      "status": "complete",
      "submit_type": null,
      "subscription": null,
      "success_url": "<hidden>",
      "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"
}
wide sandal
#

Unfortunately I won't be able to help much more than this right now I'm sorry. The first step is to find the different between your local environment and your server where the code runs and diff the exact payload you get on both and the secrets. This is really something you need to debug on your side. For example make it so that the exact same Event goes to both places to compare the behaviour

idle arch
#

There is just one place. I developed and tested it on the same exact server where it is currently still running

wide sandal
#

This does mean either you have the wrong secret or something did change in your set up or your code.

#

I'm sorry I know it's cryptic but there isn't much I can do here beyond recommending hardcoding the exact secret you want and making sure nothing in your stack is modifying the payload we send.

idle arch
#

I copied the payload from the Stripe logs and the payload I logged myself and only difference I notice is due to the way the Stripe dashboard formats the pay load. Checked the network tab to verify that it's the website and that nothing on my end is adding additional line breaks but yeah... So in the end, no difference...

wide sandal
#

I copied the payload from the Stripe logs
what does that means exactly? An event and a Request Log are completely unrelated

idle arch
#

The log from the webhook where it shows what it sent

wide sandal
#

That doesn't mean it's the real/exact payload so that doesn't really prove anything

idle arch
#

What is it then?

wide sandal
#

not sure what you mean. You're assuming you can copy-paste some text output in a browser to mirror the exact payload we sent you and I don't think that's true/correct

idle arch
#

Huh?

wide sandal
#

It's just text, it can be different from what a real HTTPS request will do to your server

#

here's my exact code https://pastebin.com/raw/xa4MaaYA
I just tried exactly that with the right webhook secret I just got on a new endpoint and that works fine for me. Can you try that

idle arch
#

Ok, I'm an idiot. Or my friend rather because I never use Stripe...๐Ÿ˜…
I didn't know that live and test mode even had different webhook secrets. Sorry for that...๐Ÿ˜…

wide sandal
#

All good

#

This is surprisingly hard to debug ๐Ÿ˜ฆ

#

We can't know if the webhook secret is the right one

idle arch
#

Yeah, I didn't know that and just saw it by accident while clicking my way through the dashboard. I now added two modes to my plugin to switch between test and live mode with their respectable stuff.
Thanks for your help. You can close this thread, if you guys do that.
Good evening