Bug: [BUG] DB constraints for events table



### Bug Description

`idempotent_event_id` column is a combination of `payment_id` and the `event_type`. Length of this field in DB can be a maximum of 64 characters. However, since `payment_id` can also be 64 characters, the resulting `idempotent_event_id` can easily exceed 64 characters leading to inconsistent behavior in the flows.

Currently, this is breaking outgoing webhooks flow.

### Expected Behavior

Outgoing webhooks flow should succeed.

### Actual Behavior

Outgoing webhooks flow breaks due to DB constraints.


```
{
    "message": "[INCOMING_WEBHOOKS_CORE - EVENT] Incoming webhook flow failed",
    "extra": {
        "error": "{\"error\":{\"type\":\"router_error\",\"code\":\"WE_03\",\"message\":\"There was some issue processing the webhook\"}}\\n├╴at crates/router/src/core/webhooks/outgoing.rs:196:18\\n├╴Failed to insert event in events table\\n├╴Incoming webhook flow for payments failed\\n│\\n╰─▶ DatabaseError: An unknown error occurred\\n    ├╴at /router/crates/diesel_models/src/query/generics.rs:89:36\\n    ├╴Error while inserting EventNew { event_id: \"evt_01979b2125017d93bb4d846d42979839\", event_type: PaymentAuthorized, event_class: Payments, is_webhook_notified: false, primary_object_id: \"AFR_202506237e491a4f-1bd8-4d97-a9a6-a2f86832bc79\", primary_object_type: PaymentDetails, created_at: 2025-06-23 4:52:10.113410262, merchant_id: Some(MerchantId(\"merchant_1741787326\")), business_profile_id: Some(ProfileId(\"pro_r3uCseg4NT35RBFpmUPA\")), primary_object_created_at: Some(2025-06-23 4:51:14.154239), idempotent_event_id: Some(\"AFR_202506237e491a4f-1bd8-4d97-a9a6-a2f86832bc79_payment_authorized\"), initial_attempt_id: Some(\"evt_01979b2125017d93bb4d846d42979839\"), request: Some(Encryption { inner: *** Encrypted data of length 7223 bytes *** }), response: None, delivery_attempt: Some(InitialAttempt), metadata: Some(Payment { payment_id: PaymentId(\"AFR_202506237e491a4f-1bd8-4d97-a9a6-a2f86832bc79\") }), is_overall_delivery_successful: Some(false) }\\n    │\\n    ╰─▶ value too long for type character varying(64)\\n        ╰╴at /router/crates/diesel_models/src/query/generics.rs:89:22\\n    ╰╴at crates/router/src/db/events.rs:129:30"
    }
}
```

### Steps To Reproduce

--

### Context For The Bug

--

### Environment

--

### Have you spent some time checking if this bug has been raised before?

- [x] I checked and didn't find a similar issue

### Have you read the Contributing Guidelines?

- [x] I have read the [Contributing Guidelines](https://github.com/juspay/hyperswitch/blob/main/docs/CONTRIBUTING.md)

### Are you willing to submit a PR?

None
diff --git a/crates/common_utils/src/consts.rs b/crates/common_utils/src/consts.rs
index 59ec5377690..bbb65e1afc6 100644
--- a/crates/common_utils/src/consts.rs
+++ b/crates/common_utils/src/consts.rs
@@ -97,6 +97,11 @@ pub const BASE64_ENGINE: base64::engine::GeneralPurpose = base64::engine::genera
 /// URL Safe base64 engine
 pub const BASE64_ENGINE_URL_SAFE: base64::engine::GeneralPurpose =
     base64::engine::general_purpose::URL_SAFE;
+
+/// URL Safe base64 engine without padding
+pub const BASE64_ENGINE_URL_SAFE_NO_PAD: base64::engine::GeneralPurpose =
+    base64::engine::general_purpose::URL_SAFE_NO_PAD;
+
 /// Regex for matching a domain
 /// Eg -
 /// http://www.example.com
diff --git a/crates/router/src/core/errors.rs b/crates/router/src/core/errors.rs
index bf5330a9325..a980bb664ef 100644
--- a/crates/router/src/core/errors.rs
+++ b/crates/router/src/core/errors.rs
@@ -157,6 +157,8 @@ pub enum WebhooksFlowError {
     OutgoingWebhookRetrySchedulingFailed,
     #[error("Outgoing webhook response encoding failed")]
     OutgoingWebhookResponseEncodingFailed,
+    #[error("ID generation failed")]
+    IdGenerationFailed,
 }
 
 impl WebhooksFlowError {
@@ -174,7 +176,8 @@ impl WebhooksFlowError {
             | Self::DisputeWebhookValidationFailed
             | Self::OutgoingWebhookEncodingFailed
             | Self::OutgoingWebhookProcessTrackerTaskUpdateFailed
-            | Self::OutgoingWebhookRetrySchedulingFailed => true,
+            | Self::OutgoingWebhookRetrySchedulingFailed
+            | Self::IdGenerationFailed => true,
         }
     }
 }
diff --git a/crates/router/src/core/webhooks/outgoing.rs b/crates/router/src/core/webhooks/outgoing.rs
index 34b49c2fc8a..adc54b09b94 100644
--- a/crates/router/src/core/webhooks/outgoing.rs
+++ b/crates/router/src/core/webhooks/outgoing.rs
@@ -60,7 +60,9 @@ pub(crate) async fn create_event_and_trigger_outgoing_webhook(
 ) -> CustomResult<(), errors::ApiErrorResponse> {
     let delivery_attempt = enums::WebhookDeliveryAttempt::InitialAttempt;
     let idempotent_event_id =
-        utils::get_idempotent_event_id(&primary_object_id, event_type, delivery_attempt);
+        utils::get_idempotent_event_id(&primary_object_id, event_type, delivery_attempt)
+            .change_context(errors::ApiErrorResponse::WebhookProcessingFailure)
+            .attach_printable("Failed to generate idempotent event ID")?;
     let webhook_url_result = get_webhook_url_from_business_profile(&business_profile);
 
     if !state.conf.webhooks.outgoing_enabled
diff --git a/crates/router/src/core/webhooks/outgoing_v2.rs b/crates/router/src/core/webhooks/outgoing_v2.rs
index 07758817e68..d0e0ecaa69a 100644
--- a/crates/router/src/core/webhooks/outgoing_v2.rs
+++ b/crates/router/src/core/webhooks/outgoing_v2.rs
@@ -48,7 +48,9 @@ pub(crate) async fn create_event_and_trigger_outgoing_webhook(
 ) -> CustomResult<(), errors::ApiErrorResponse> {
     let delivery_attempt = enums::WebhookDeliveryAttempt::InitialAttempt;
     let idempotent_event_id =
-        utils::get_idempotent_event_id(&primary_object_id, event_type, delivery_attempt);
+        utils::get_idempotent_event_id(&primary_object_id, event_type, delivery_attempt)
+            .change_context(errors::ApiErrorResponse::WebhookProcessingFailure)
+            .attach_printable("Failed to generate idempotent event ID")?;
     let webhook_url_result = business_profile
         .get_webhook_url_from_profile()
         .change_context(errors::WebhooksFlowError::MerchantWebhookUrlNotConfigured);
diff --git a/crates/router/src/core/webhooks/utils.rs b/crates/router/src/core/webhooks/utils.rs
index 45b6c24a2ed..0344d7d010e 100644
--- a/crates/router/src/core/webhooks/utils.rs
+++ b/crates/router/src/core/webhooks/utils.rs
@@ -1,6 +1,12 @@
 use std::marker::PhantomData;
 
-use common_utils::{errors::CustomResult, ext_traits::ValueExt};
+use base64::Engine;
+use common_utils::{
+    consts,
+    crypto::{self, GenerateDigest},
+    errors::CustomResult,
+    ext_traits::ValueExt,
+};
 use error_stack::{Report, ResultExt};
 use redis_interface as redis;
 use router_env::tracing;
@@ -146,18 +152,28 @@ pub(crate) fn get_idempotent_event_id(
     primary_object_id: &str,
     event_type: types::storage::enums::EventType,
     delivery_attempt: types::storage::enums::WebhookDeliveryAttempt,
-) -> String {
+) -> Result<String, Report<errors::WebhooksFlowError>> {
     use crate::types::storage::enums::WebhookDeliveryAttempt;
 
     const EVENT_ID_SUFFIX_LENGTH: usize = 8;
 
     let common_prefix = format!("{primary_object_id}_{event_type}");
-    match delivery_attempt {
-        WebhookDeliveryAttempt::InitialAttempt => common_prefix,
+
+    // Hash the common prefix with SHA256 and encode with URL-safe base64 without padding
+    let digest = crypto::Sha256
+        .generate_digest(common_prefix.as_bytes())
+        .change_context(errors::WebhooksFlowError::IdGenerationFailed)
+        .attach_printable("Failed to generate idempotent event ID")?;
+    let base_encoded = consts::BASE64_ENGINE_URL_SAFE_NO_PAD.encode(digest);
+
+    let result = match delivery_attempt {
+        WebhookDeliveryAttempt::InitialAttempt => base_encoded,
         WebhookDeliveryAttempt::AutomaticRetry | WebhookDeliveryAttempt::ManualRetry => {
-            common_utils::generate_id(EVENT_ID_SUFFIX_LENGTH, &common_prefix)
+            common_utils::generate_id(EVENT_ID_SUFFIX_LENGTH, &base_encoded)
         }
-    }
+    };
+
+    Ok(result)
 }
 
 #[inline]
diff --git a/crates/router/src/core/webhooks/webhook_events.rs b/crates/router/src/core/webhooks/webhook_events.rs
index 44ce4fdd515..700295af0ec 100644
--- a/crates/router/src/core/webhooks/webhook_events.rs
+++ b/crates/router/src/core/webhooks/webhook_events.rs
@@ -287,7 +287,9 @@ pub async fn retry_delivery_attempt(
         &event_to_retry.primary_object_id,
         event_to_retry.event_type,
         delivery_attempt,
-    );
+    )
+    .change_context(errors::ApiErrorResponse::WebhookProcessingFailure)
+    .attach_printable("Failed to generate idempotent event ID")?;
 
     let now = common_utils::date_time::now();
     let new_event = domain::Event {
diff --git a/crates/router/src/workflows/outgoing_webhook_retry.rs b/crates/router/src/workflows/outgoing_webhook_retry.rs
index 559f62b1a4a..d84f0b26dc7 100644
--- a/crates/router/src/workflows/outgoing_webhook_retry.rs
+++ b/crates/router/src/workflows/outgoing_webhook_retry.rs
@@ -72,7 +72,9 @@ impl ProcessTrackerWorkflow<SessionState> for OutgoingWebhookRetryWorkflow {
             &tracking_data.primary_object_id,
             tracking_data.event_type,
             delivery_attempt,
-        );
+        )
+        .change_context(errors::ApiErrorResponse::WebhookProcessingFailure)
+        .attach_printable("Failed to generate idempotent event ID")?;
 
         let initial_event = match &tracking_data.initial_attempt_id {
             Some(initial_attempt_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 -->
This change updates the idempotent_event_id generation to first hash the base string with SHA256 and then encode it using URL-safe Base64 without padding. By doing this, the generated IDs are guaranteed to be short and deterministic, ensuring they always fit within the 64-character database column limit. This fix addresses the issue where long payment_ids combined with event types were exceeding the DB constraint and breaking the outgoing webhooks flow.


### 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).
-->
The idempotent_event_id column is a combination of payment_id and event_type. The DB column length is limited to 64 characters. Since payment_id itself can be up to 64 characters long, appending event_type causes the field to exceed the limit, leading to database constraint violations.

Currently, this breaks the outgoing webhooks flow.

Additionally, during staggered deployments and rollback, the uniqueness of these IDs may cause dual webhook dispatches.

closes #8460

## 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)?
-->
1. Started the local Node.js Express webhook listener on port 3000 using the server.js script.
<details>
<summary>server.js script</summary>

```
mkdir -p /tmp/hs-webhooks && cd /tmp/hs-webhooks
npm init -y >/dev/null
npm i express >/dev/null

cat > server.js <<'JS'
const express = require('express');
const app = express();
app.use(express.json({ type: '*/*' }));

app.post('/webhooks/hs', (req, res) => {
  console.log('---- Webhook received ----');
  console.log('Headers:', req.headers);
  console.log('Body:', JSON.stringify(req.body, null, 2));
  res.sendStatus(200);
});

app.listen(3000, () => console.log('Webhook listener running on port 3000'));
JS

node server.js
```
</details>

2. **Merchant Creation**

<details>
<summary>Merchant creation Curl</summary>

```bash
curl --location 'http://localhost:8080/accounts' \
--header 'Content-Type: application/json' \
--header 'Accept: application/json' \
--header 'api-key: test_admin' \
--data-raw '{
  "merchant_id": "merchant_1758029612",
  "locker_id": "m0010",
  "merchant_name": "worldpay",
  "merchant_details": {
    "primary_contact_person": "John Test",
    "primary_email": "JohnTest@test.com",
    "primary_phone": "sunt laborum",
    "secondary_contact_person": "John Test2",
    "secondary_email": "JohnTest2@test.com",
    "secondary_phone": "cillum do dolor id",
    "website": "https://www.example.com",
    "about_business": "Online Retail with a wide selection of organic products for North America",
    "address": {
      "line1": "1467",
      "line2": "Harrison Street",
      "line3": "Harrison Street",
      "city": "San Fransico",
      "state": "California",
      "zip": "94122",
      "country": "US",
      "first_name":"john",
      "last_name":"Doe"
    }
  },
  "return_url": "https://google.com/success",
  "webhook_details": {
    "webhook_version": "1.0.1",
    "webhook_username": "ekart_retail",
    "webhook_password": "password_ekart@123",
    "webhook_url":"http://localhost:3000/webhooks/hs",
    "payment_created_enabled": true,
    "payment_succeeded_enabled": true,
    "payment_failed_enabled": true
  },
  "sub_merchants_enabled": false,
  "parent_merchant_id":"merchant_123",
  "metadata": {
    "city": "NY",
    "unit": "245",
    "merchant_name": "Acme Widgets LLC"
  },
  "primary_business_details": [
    {
      "country": "US",
      "business": "default"
    }
  ]
}'
```
</details>

After this when i created payment it created event with these idempotent_event_id.
<img width="533" height="165" alt="image" src="https://github.com/user-attachments/assets/6610ede4-6ffe-4de1-bde3-664ac00abec4" />


<details>
<summary>Manual Retry Curl</summary>

```
curl --location --request POST 'http://localhost:8080/events/merchant_1758190753/evt_01995ce5346575608391d7f8b825175d/retry' \
--header 'api-key: test_admin'
```

</details>

<img width="559" height="252" alt="image" src="https://github.com/user-attachments/assets/cb652d7b-5749-48f0-b2f7-78726411e789" />

For primary object id greater than 64 chars still generated idempotent_event_id will always be less than 64 characters in length.

<img width="831" height="33" alt="image" src="https://github.com/user-attachments/assets/3d7b838a-df61-401e-ac2c-fd7e5cd5b4c8" />


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

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