Bug: [BUG] Webhook source verification is `false` for NMI



[Connector documentation](https://docs.nmi.com/reference/overview) php example says this:

```php
  function webhookIsVerified($webhookBody, $signingKey, $nonce, $sig) {
    return $sig === hash_hmac("sha256", $nonce . "." . $webhookBody, $signingKey);
  }

  try {
    $signingKey = "YOUR_SIGNING_KEY_HERE";
    $webhookBody = file_get_contents("php://input");
    $headers = getallheaders();
    $sigHeader = $headers['Webhook-Signature'];

    if (!is_null($sigHeader) && strlen($sigHeader) < 1) {
      throw new Exception("invalid webhook - signature header missing");
    }

    if (preg_match('/t=(.*),s=(.*)/', $sigHeader, $matches)) {
      $nonce = $matches[1];
      $signature = $matches[2];
    } else {
      throw new Exception("unrecognized webhook signature format");
    }

    if (!webhookIsVerified($webhookBody, $signingKey, $nonce, $signature)) {
      throw new Exception("invalid webhook - invalid signature, cannot verify sender");
    }

    // webhook is now verified to have been sent by us, continue processing

    echo "webhook is verified";
    $webhook = json_decode($webhookBody);
    var_export($webhook);
  } catch (Exception $e) {
    echo "error: $e\n";
  }
```

In Hyperswitch code:

`signature` and `nonce` extraction is wrong. We are extracting `nonce` instead of the signature from the `Webhook-Signature` header and the entire match instead of just the `nonce` value here:

- `captures.get(0)` -- entire match instead of nonce, because 0 contains entire regex match
- `captures.get(1)` -- nonce instead of signature
diff --git a/crates/hyperswitch_connectors/src/connectors/nmi.rs b/crates/hyperswitch_connectors/src/connectors/nmi.rs
index 7ae21664e73..50e5db86e5a 100644
--- a/crates/hyperswitch_connectors/src/connectors/nmi.rs
+++ b/crates/hyperswitch_connectors/src/connectors/nmi.rs
@@ -9,6 +9,7 @@ use common_utils::{
     types::{AmountConvertor, FloatMajorUnit, FloatMajorUnitForConnector},
 };
 use error_stack::{report, ResultExt};
+use hex;
 use hyperswitch_domain_models::{
     router_data::{AccessToken, ErrorResponse, RouterData},
     router_flow_types::{
@@ -859,13 +860,15 @@ impl IncomingWebhook for Nmi {
             .captures(sig_header)
         {
             let signature = captures
-                .get(1)
+                .get(2)
                 .ok_or(ConnectorError::WebhookSignatureNotFound)?
                 .as_str();
-            return Ok(signature.as_bytes().to_vec());
-        }
 
-        Err(report!(ConnectorError::WebhookSignatureNotFound))
+            // Decode hex signature to bytes
+            hex::decode(signature).change_context(ConnectorError::WebhookSignatureNotFound)
+        } else {
+            Err(report!(ConnectorError::WebhookSignatureNotFound))
+        }
     }
 
     fn get_webhook_source_verification_message(
@@ -883,15 +886,16 @@ impl IncomingWebhook for Nmi {
             .captures(sig_header)
         {
             let nonce = captures
-                .get(0)
+                .get(1)
                 .ok_or(ConnectorError::WebhookSignatureNotFound)?
                 .as_str();
 
             let message = format!("{}.{}", nonce, String::from_utf8_lossy(request.body));
 
-            return Ok(message.into_bytes());
+            Ok(message.into_bytes())
+        } else {
+            Err(report!(ConnectorError::WebhookSignatureNotFound))
         }
-        Err(report!(ConnectorError::WebhookSignatureNotFound))
     }
 
     fn get_webhook_object_reference_id(

## Type of Change
<!-- Put an `x` in the boxes that apply -->

- [x] Bugfix
- [ ] New feature
- [ ] Enhancement
- [ ] Refactoring
- [ ] Dependency updates
- [ ] Documentation
- [ ] CI/CD

## Description
<!-- Describe your changes in detail -->

the problem is that we previously extracted `nonce` instead of `signature` and entire regex match instead of `nonce`. this pr fixes webhook source verification for nmi connector by properly extracting `signature` (`2`) and `nonce` (`1`) values.

check attached issue for detailed explanation.

### Additional Changes

- [ ] This PR modifies the API contract
- [ ] This PR modifies the database schema
- [ ] This PR modifies application configuration/environment variables

<!--
Provide links to the files with corresponding changes.

Following are the paths where you can find config files:
1. `config`
2. `crates/router/src/configs`
3. `loadtest/config`
-->


## Motivation and Context
<!--
Why is this change required? What problem does it solve?
If it fixes an open issue, please link to the issue here.

If you don't have an issue, we'd recommend starting with one first so the PR
can focus on the implementation (unless it is an obvious bug or documentation fix
that will have little conversation).
-->

webhook source verification must not be `false` unless connector does not support it. in this case, it was a mistake from our end where we parsed wrong values.

closes https://github.com/juspay/hyperswitch/issues/8945

## How did you test it?
<!--
Did you write an integration/unit/API test to verify the code changes?
Or did you test this change manually (provide relevant screenshots)?
-->

<details>

<summary>Setup webhooks for NMI connector and also the signature verification</summary>

<img width="1212" height="130" alt="image" src="https://github.com/user-attachments/assets/3759cb84-57f6-441e-845e-d6c53868764a" />
<img width="385" height="30" alt="image" src="https://github.com/user-attachments/assets/2df31bc8-54a4-4779-b112-433526318be3" />
<img width="193" height="55" alt="image" src="https://github.com/user-attachments/assets/eeb4287f-e309-49b0-9943-19334a8c7994" />

</details>

<details>

<summary>Make a Payment</summary>

```curl
curl --location 'https://pix-mbp.orthrus-monster.ts.net/payments' \
--header 'Content-Type: application/json' \
--header 'Accept: application/json' \
--header 'api-key: dev_A3aKSnvxEuazXkjOdUMlrNTSpmIlH9MlaWJj9Ax9xEpFqGN4aVBUGINJjkjSlRdz' \
--data-raw '{
    "amount": 60159,
    "currency": "USD",
    "confirm": true,
    "capture_method": "automatic",
    "capture_on": "2022-09-10T10:11:12Z",
    "amount_to_capture": 60159,
    "customer_id": "StripeCustomer",
    "email": "guest@example.com",
    "name": "John Doe",
    "phone": "999999999",
    "phone_country_code": "+1",
    "description": "Its my first payment request",
    "authentication_type": "no_three_ds",
    "return_url": "https://duck.com",
    "payment_method": "card",
    "payment_method_data": {
        "card": {
            "card_number": "4111111111111111",
            "card_exp_month": "10",
            "card_exp_year": "25",
            "card_holder_name": "joseph Doe",
            "card_cvc": "123"
        }
    },
    "billing": {
        "address": {
            "line1": "1467",
            "line2": "Harrison Street",
            "line3": "Harrison Street",
            "city": "San Fransico",
            "state": "California",
            "zip": "94122",
            "country": "US",
            "first_name": "PiX"
        }
    },
    "shipping": {
        "address": {
            "line1": "1467",
            "line2": "Harrison Street",
            "line3": "Harrison Street",
            "city": "San Fransico",
            "state": "California",
            "zip": "94122",
            "country": "US",
            "first_name": "PiX"
        }
    },
    "statement_descriptor_name": "joseph",
    "statement_descriptor_suffix": "JS",
    "metadata": {
        "udf1": "value1",
        "new_customer": "true",
        "login_date": "2019-09-10T10:11:12Z"
    }
}'
```
```json
{
	"payment_id": "pay_46yfYfNcSmMZ847bso0p",
	"merchant_id": "postman_merchant_GHAction_1755093166",
	"status": "succeeded",
	"amount": 15429,
	"net_amount": 15429,
	"shipping_cost": null,
	"amount_capturable": 0,
	"amount_received": 15429,
	"connector": "nmi",
	"client_secret": "pay_46yfYfNcSmMZ847bso0p_secret_55lzxYdXlKlBI80u1Y8w",
	"created": "2025-08-13T14:25:56.929Z",
	"currency": "USD",
	"customer_id": "StripeCustomer",
	"customer": {
		"id": "StripeCustomer",
		"name": "John Doe",
		"email": "guest@example.com",
		"phone": "999999999",
		"phone_country_code": "+1"
	},
	"description": "Its my first payment request",
	"refunds": null,
	"disputes": null,
	"mandate_id": null,
	"mandate_data": null,
	"setup_future_usage": null,
	"off_session": null,
	"capture_on": null,
	"capture_method": "automatic",
	"payment_method": "card",
	"payment_method_data": {
		"card": {
			"last4": "1111",
			"card_type": "CREDIT",
			"card_network": "Visa",
			"card_issuer": "JP Morgan",
			"card_issuing_country": "INDIA",
			"card_isin": "411111",
			"card_extended_bin": null,
			"card_exp_month": "10",
			"card_exp_year": "25",
			"card_holder_name": "joseph Doe",
			"payment_checks": null,
			"authentication_data": null
		},
		"billing": null
	},
	"payment_token": "token_0k7Cgt2Xh8LXsZAxF3gz",
	"shipping": {
		"address": {
			"city": "San Fransico",
			"country": "US",
			"line1": "1467",
			"line2": "Harrison Street",
			"line3": "Harrison Street",
			"zip": "94122",
			"state": "California",
			"first_name": "PiX",
			"last_name": null,
			"origin_zip": null
		},
		"phone": null,
		"email": null
	},
	"billing": {
		"address": {
			"city": "San Fransico",
			"country": "US",
			"line1": "1467",
			"line2": "Harrison Street",
			"line3": "Harrison Street",
			"zip": "94122",
			"state": "California",
			"first_name": "PiX",
			"last_name": null,
			"origin_zip": null
		},
		"phone": null,
		"email": null
	},
	"order_details": null,
	"email": "guest@example.com",
	"name": "John Doe",
	"phone": "999999999",
	"return_url": "https://duck.com/",
	"authentication_type": "no_three_ds",
	"statement_descriptor_name": "joseph",
	"statement_descriptor_suffix": "JS",
	"next_action": null,
	"cancellation_reason": null,
	"error_code": null,
	"error_message": null,
	"unified_code": null,
	"unified_message": null,
	"payment_experience": null,
	"payment_method_type": "credit",
	"connector_label": null,
	"business_country": null,
	"business_label": "default",
	"business_sub_label": null,
	"allowed_payment_method_types": null,
	"ephemeral_key": {
		"customer_id": "StripeCustomer",
		"created_at": 1755095156,
		"expires": 1755098756,
		"secret": "epk_12014a82ba5c4c97980e8e0ff9a64b7a"
	},
	"manual_retry_allowed": false,
	"connector_transaction_id": "11025492335",
	"frm_message": null,
	"metadata": {
		"udf1": "value1",
		"login_date": "2019-09-10T10:11:12Z",
		"new_customer": "true"
	},
	"connector_metadata": null,
	"feature_metadata": {
		"redirect_response": null,
		"search_tags": null,
		"apple_pay_recurring_details": null,
		"gateway_system": "direct"
	},
	"reference_id": "pay_46yfYfNcSmMZ847bso0p_1",
	"payment_link": null,
	"profile_id": "pro_9TabQmBmCO93BbyuDbqZ",
	"surcharge_details": null,
	"attempt_count": 1,
	"merchant_decision": null,
	"merchant_connector_id": "mca_kMejAtGh3Rsbobtc3dOq",
	"incremental_authorization_allowed": false,
	"authorization_count": null,
	"incremental_authorizations": null,
	"external_authentication_details": null,
	"external_3ds_authentication_attempted": false,
	"expires_on": "2025-08-13T14:40:56.929Z",
	"fingerprint": null,
	"browser_info": null,
	"payment_channel": null,
	"payment_method_id": null,
	"network_transaction_id": null,
	"payment_method_status": null,
	"updated": "2025-08-13T14:25:58.919Z",
	"split_payments": null,
	"frm_metadata": null,
	"extended_authorization_applied": null,
	"capture_before": null,
	"merchant_order_reference_id": null,
	"order_tax_amount": null,
	"connector_mandate_id": null,
	"card_discovery": "manual",
	"force_3ds_challenge": false,
	"force_3ds_challenge_trigger": false,
	"issuer_error_code": null,
	"issuer_error_message": null,
	"is_iframe_redirection_enabled": null,
	"whole_connector_response": null,
	"enable_partial_authorization": null
}
```

</details>

<details>

<summary>In logs, source verified should be true</summary>

```log
  2025-08-13T14:25:59.722386Z  INFO router::core::webhooks::incoming: source_verified: true
    at crates/router/src/core/webhooks/incoming.rs:597
    in router::core::webhooks::incoming::incoming_webhooks_core
    in router::services::api::server_wrap_util with flow: IncomingWebhookReceive, lock_action: NotApplicable, merchant_id: "postman_merchant_GHAction_1755093166"
    in router::services::api::server_wrap with flow: IncomingWebhookReceive, lock_action: NotApplicable, request_method: "POST", request_url_path: "/webhooks/postman_merchant_GHAction_1755093166/nmi"
    in router::routes::webhooks::receive_incoming_webhook with flow: IncomingWebhookReceive
    in router::middleware::ROOT_SPAN with flow: "UNKNOWN"
```

</details>

## Checklist
<!-- Put an `x` in the boxes that apply -->

- [x] I formatted the code `cargo +nightly fmt --all`
- [x] I addressed lints thrown by `just clippy && just clippy_v2`
- [x] I reviewed the submitted code
- [ ] I added unit tests for my changes where possible
