Bug: [FEATURE] Add webhook support for network tokenization



### Feature Description

Add webhook support to consume the webhooks from network token requestor.

### Possible Implementation

New webhook wrapper should be introduced, since current implementation are specific to payments, but payment methods.

### Have you spent some time checking if this feature request 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?

Yes, I am willing to submit a PR!
diff --git a/crates/api_models/src/payment_methods.rs b/crates/api_models/src/payment_methods.rs
index 25b583f5dc4..c681cf7edd0 100644
--- a/crates/api_models/src/payment_methods.rs
+++ b/crates/api_models/src/payment_methods.rs
@@ -1065,6 +1065,23 @@ pub struct Card {
     pub nick_name: Option<String>,
 }
 
+#[cfg(feature = "v1")]
+impl From<(Card, Option<common_enums::CardNetwork>)> for CardDetail {
+    fn from((card, card_network): (Card, Option<common_enums::CardNetwork>)) -> Self {
+        Self {
+            card_number: card.card_number.clone(),
+            card_exp_month: card.card_exp_month.clone(),
+            card_exp_year: card.card_exp_year.clone(),
+            card_holder_name: card.name_on_card.clone(),
+            nick_name: card.nick_name.map(masking::Secret::new),
+            card_issuing_country: None,
+            card_network,
+            card_issuer: None,
+            card_type: None,
+        }
+    }
+}
+
 #[cfg(feature = "v1")]
 #[derive(Debug, serde::Deserialize, serde::Serialize, Clone, ToSchema)]
 pub struct CardDetailFromLocker {
diff --git a/crates/api_models/src/webhooks.rs b/crates/api_models/src/webhooks.rs
index 7895588e8e0..4667866073b 100644
--- a/crates/api_models/src/webhooks.rs
+++ b/crates/api_models/src/webhooks.rs
@@ -129,6 +129,11 @@ pub enum WebhookResponseTracker {
         mandate_id: String,
         status: common_enums::MandateStatus,
     },
+    #[cfg(feature = "v1")]
+    PaymentMethod {
+        payment_method_id: String,
+        status: common_enums::PaymentMethodStatus,
+    },
     NoEffect,
     Relay {
         relay_id: common_utils::id_type::RelayId,
@@ -143,13 +148,30 @@ impl WebhookResponseTracker {
             Self::Payment { payment_id, .. }
             | Self::Refund { payment_id, .. }
             | Self::Dispute { payment_id, .. } => Some(payment_id.to_owned()),
-            Self::NoEffect | Self::Mandate { .. } => None,
+            Self::NoEffect | Self::Mandate { .. } | Self::PaymentMethod { .. } => None,
             #[cfg(feature = "payouts")]
             Self::Payout { .. } => None,
             Self::Relay { .. } => None,
         }
     }
 
+    #[cfg(feature = "v1")]
+    pub fn get_payment_method_id(&self) -> Option<String> {
+        match self {
+            Self::PaymentMethod {
+                payment_method_id, ..
+            } => Some(payment_method_id.to_owned()),
+            Self::Payment { .. }
+            | Self::Refund { .. }
+            | Self::Dispute { .. }
+            | Self::NoEffect
+            | Self::Mandate { .. }
+            | Self::Relay { .. } => None,
+            #[cfg(feature = "payouts")]
+            Self::Payout { .. } => None,
+        }
+    }
+
     #[cfg(feature = "v2")]
     pub fn get_payment_id(&self) -> Option<common_utils::id_type::GlobalPaymentId> {
         match self {
diff --git a/crates/common_enums/src/enums.rs b/crates/common_enums/src/enums.rs
index d7f4a1b2a20..d36b2d624f0 100644
--- a/crates/common_enums/src/enums.rs
+++ b/crates/common_enums/src/enums.rs
@@ -8535,3 +8535,23 @@ impl RoutingApproach {
         }
     }
 }
+
+#[derive(
+    Clone,
+    Copy,
+    Debug,
+    Eq,
+    PartialEq,
+    serde::Serialize,
+    serde::Deserialize,
+    ToSchema,
+    strum::Display,
+    strum::EnumString,
+    Hash,
+)]
+#[serde(rename_all = "snake_case")]
+#[strum(serialize_all = "snake_case")]
+#[router_derive::diesel_enum(storage_type = "text")]
+pub enum CallbackMapperIdType {
+    NetworkTokenRequestorRefernceID,
+}
diff --git a/crates/common_types/src/callback_mapper.rs b/crates/common_types/src/callback_mapper.rs
new file mode 100644
index 00000000000..c7af35d1c79
--- /dev/null
+++ b/crates/common_types/src/callback_mapper.rs
@@ -0,0 +1,40 @@
+use common_utils::id_type;
+use diesel::{AsExpression, FromSqlRow};
+
+#[derive(
+    Clone, Debug, Eq, PartialEq, serde::Deserialize, serde::Serialize, AsExpression, FromSqlRow,
+)]
+#[diesel(sql_type = diesel::sql_types::Jsonb)]
+/// Represents the data associated with a callback mapper.
+pub enum CallbackMapperData {
+    /// data variant used while processing the network token webhook
+    NetworkTokenWebhook {
+        /// Merchant id assiociated with the network token requestor reference id
+        merchant_id: id_type::MerchantId,
+        /// Payment Method id assiociated with the network token requestor reference id
+        payment_method_id: String,
+        /// Customer id assiociated with the network token requestor reference id
+        customer_id: id_type::CustomerId,
+    },
+}
+
+impl CallbackMapperData {
+    /// Retrieves the details of the network token webhook type from callback mapper data.
+    pub fn get_network_token_webhook_details(
+        &self,
+    ) -> (id_type::MerchantId, String, id_type::CustomerId) {
+        match self {
+            Self::NetworkTokenWebhook {
+                merchant_id,
+                payment_method_id,
+                customer_id,
+            } => (
+                merchant_id.clone(),
+                payment_method_id.clone(),
+                customer_id.clone(),
+            ),
+        }
+    }
+}
+
+common_utils::impl_to_sql_from_sql_json!(CallbackMapperData);
diff --git a/crates/common_types/src/lib.rs b/crates/common_types/src/lib.rs
index 4913f1ebcc0..91ee876184a 100644
--- a/crates/common_types/src/lib.rs
+++ b/crates/common_types/src/lib.rs
@@ -12,3 +12,6 @@ pub mod primitive_wrappers;
 pub mod refunds;
 /// types for three ds decision rule engine
 pub mod three_ds_decision_rule_engine;
+
+///types for callback mapper
+pub mod callback_mapper;
diff --git a/crates/common_utils/src/events.rs b/crates/common_utils/src/events.rs
index 506211c4d7a..376b57fbb47 100644
--- a/crates/common_utils/src/events.rs
+++ b/crates/common_utils/src/events.rs
@@ -75,6 +75,10 @@ pub enum ApiEventsType {
         connector: String,
         payment_id: Option<id_type::PaymentId>,
     },
+    #[cfg(feature = "v1")]
+    NetworkTokenWebhook {
+        payment_method_id: Option<String>,
+    },
     #[cfg(feature = "v2")]
     Webhooks {
         connector: id_type::MerchantConnectorAccountId,
diff --git a/crates/diesel_models/src/callback_mapper.rs b/crates/diesel_models/src/callback_mapper.rs
index 3e031d483ac..f9a8fa47c87 100644
--- a/crates/diesel_models/src/callback_mapper.rs
+++ b/crates/diesel_models/src/callback_mapper.rs
@@ -1,4 +1,5 @@
-use common_utils::pii;
+use common_enums::enums as common_enums;
+use common_types::callback_mapper::CallbackMapperData;
 use diesel::{Identifiable, Insertable, Queryable, Selectable};
 
 use crate::schema::callback_mapper;
@@ -7,8 +8,8 @@ use crate::schema::callback_mapper;
 #[diesel(table_name = callback_mapper,  primary_key(id, type_), check_for_backend(diesel::pg::Pg))]
 pub struct CallbackMapper {
     pub id: String,
-    pub type_: String,
-    pub data: pii::SecretSerdeValue,
+    pub type_: common_enums::CallbackMapperIdType,
+    pub data: CallbackMapperData,
     pub created_at: time::PrimitiveDateTime,
     pub last_modified_at: time::PrimitiveDateTime,
 }
diff --git a/crates/hyperswitch_domain_models/src/callback_mapper.rs b/crates/hyperswitch_domain_models/src/callback_mapper.rs
index fffc33e572a..429f3f7ff95 100644
--- a/crates/hyperswitch_domain_models/src/callback_mapper.rs
+++ b/crates/hyperswitch_domain_models/src/callback_mapper.rs
@@ -1,12 +1,29 @@
-use common_utils::pii;
-use serde::{self, Deserialize, Serialize};
+use common_enums::enums as common_enums;
+use common_types::callback_mapper::CallbackMapperData;
 
-#[derive(Clone, Debug, Eq, PartialEq, Serialize, Deserialize)]
+#[derive(Clone, Debug, Eq, PartialEq)]
 pub struct CallbackMapper {
     pub id: String,
-    #[serde(rename = "type")]
-    pub type_: String,
-    pub data: pii::SecretSerdeValue,
+    pub callback_mapper_id_type: common_enums::CallbackMapperIdType,
+    pub data: CallbackMapperData,
     pub created_at: time::PrimitiveDateTime,
     pub last_modified_at: time::PrimitiveDateTime,
 }
+
+impl CallbackMapper {
+    pub fn new(
+        id: String,
+        callback_mapper_id_type: common_enums::CallbackMapperIdType,
+        data: CallbackMapperData,
+        created_at: time::PrimitiveDateTime,
+        last_modified_at: time::PrimitiveDateTime,
+    ) -> Self {
+        Self {
+            id,
+            callback_mapper_id_type,
+            data,
+            created_at,
+            last_modified_at,
+        }
+    }
+}
diff --git a/crates/router/src/configs/secrets_transformers.rs b/crates/router/src/configs/secrets_transformers.rs
index b4c579a07a8..9129a9a78f6 100644
--- a/crates/router/src/configs/secrets_transformers.rs
+++ b/crates/router/src/configs/secrets_transformers.rs
@@ -314,11 +314,15 @@ impl SecretsHandler for settings::NetworkTokenizationService {
         let private_key = secret_management_client
             .get_secret(network_tokenization.private_key.clone())
             .await?;
+        let webhook_source_verification_key = secret_management_client
+            .get_secret(network_tokenization.webhook_source_verification_key.clone())
+            .await?;
 
         Ok(value.transition_state(|network_tokenization| Self {
             public_key,
             private_key,
             token_service_api_key,
+            webhook_source_verification_key,
             ..network_tokenization
         }))
     }
diff --git a/crates/router/src/configs/settings.rs b/crates/router/src/configs/settings.rs
index 9424b2572da..8a2132a7fe1 100644
--- a/crates/router/src/configs/settings.rs
+++ b/crates/router/src/configs/settings.rs
@@ -601,6 +601,7 @@ pub struct NetworkTokenizationService {
     pub key_id: String,
     pub delete_token_url: url::Url,
     pub check_token_status_url: url::Url,
+    pub webhook_source_verification_key: Secret<String>,
 }
 
 #[derive(Debug, Deserialize, Clone, Default)]
diff --git a/crates/router/src/configs/validations.rs b/crates/router/src/configs/validations.rs
index 1b8adfaa8b8..ab5f2af4160 100644
--- a/crates/router/src/configs/validations.rs
+++ b/crates/router/src/configs/validations.rs
@@ -244,7 +244,16 @@ impl super::settings::NetworkTokenizationService {
             Err(ApplicationError::InvalidConfigurationValueError(
                 "private_key must not be empty".into(),
             ))
-        })
+        })?;
+
+        when(
+            self.webhook_source_verification_key.is_default_or_empty(),
+            || {
+                Err(ApplicationError::InvalidConfigurationValueError(
+                    "webhook_source_verification_key must not be empty".into(),
+                ))
+            },
+        )
     }
 }
 
diff --git a/crates/router/src/core/payment_methods.rs b/crates/router/src/core/payment_methods.rs
index 59be79c66f6..2034119fa21 100644
--- a/crates/router/src/core/payment_methods.rs
+++ b/crates/router/src/core/payment_methods.rs
@@ -20,16 +20,16 @@ use api_models::payment_methods;
 #[cfg(feature = "payouts")]
 pub use api_models::{enums::PayoutConnectors, payouts as payout_types};
 #[cfg(feature = "v1")]
-use common_utils::ext_traits::{Encode, OptionExt};
-use common_utils::{consts::DEFAULT_LOCALE, id_type};
+use common_utils::{consts::DEFAULT_LOCALE, ext_traits::OptionExt};
 #[cfg(feature = "v2")]
 use common_utils::{
     crypto::Encryptable,
     errors::CustomResult,
-    ext_traits::{AsyncExt, Encode, ValueExt},
+    ext_traits::{AsyncExt, ValueExt},
     fp_utils::when,
     generate_id, types as util_types,
 };
+use common_utils::{ext_traits::Encode, id_type};
 use diesel_models::{
     enums, GenericLinkNew, PaymentMethodCollectLink, PaymentMethodCollectLinkData,
 };
diff --git a/crates/router/src/core/payments/tokenization.rs b/crates/router/src/core/payments/tokenization.rs
index b88476e19d8..ef9e8b580dd 100644
--- a/crates/router/src/core/payments/tokenization.rs
+++ b/crates/router/src/core/payments/tokenization.rs
@@ -7,6 +7,7 @@ use api_models::{
     payment_methods::PaymentMethodDataWalletInfo, payments::ConnectorMandateReferenceId,
 };
 use common_enums::{ConnectorMandateStatus, PaymentMethod};
+use common_types::callback_mapper::CallbackMapperData;
 use common_utils::{
     crypto::Encryptable,
     ext_traits::{AsyncExt, Encode, ValueExt},
@@ -17,6 +18,7 @@ use common_utils::{
 use error_stack::{report, ResultExt};
 #[cfg(feature = "v1")]
 use hyperswitch_domain_models::{
+    callback_mapper::CallbackMapper,
     mandates::{CommonMandateReference, PaymentsMandateReference, PaymentsMandateReferenceRecord},
     payment_method_data,
 };
@@ -754,11 +756,45 @@ where
                                         card.card_network
                                             .map(|card_network| card_network.to_string())
                                     }),
-                                    network_token_requestor_ref_id,
+                                    network_token_requestor_ref_id.clone(),
                                     network_token_locker_id,
                                     pm_network_token_data_encrypted,
                                 )
                                 .await?;
+
+                            match network_token_requestor_ref_id {
+                                Some(network_token_requestor_ref_id) => {
+                                    //Insert the network token reference ID along with merchant id, customer id in CallbackMapper table for its respective webooks
+                                    let callback_mapper_data =
+                                        CallbackMapperData::NetworkTokenWebhook {
+                                            merchant_id: merchant_context
+                                                .get_merchant_account()
+                                                .get_id()
+                                                .clone(),
+                                            customer_id,
+                                            payment_method_id: resp.payment_method_id.clone(),
+                                        };
+                                    let callback_mapper = CallbackMapper::new(
+                                        network_token_requestor_ref_id,
+                                        common_enums::CallbackMapperIdType::NetworkTokenRequestorRefernceID,
+                                        callback_mapper_data,
+                                        common_utils::date_time::now(),
+                                        common_utils::date_time::now(),
+                                    );
+
+                                    db.insert_call_back_mapper(callback_mapper)
+                                        .await
+                                        .change_context(
+                                            errors::ApiErrorResponse::InternalServerError,
+                                        )
+                                        .attach_printable(
+                                            "Failed to insert in Callback Mapper table",
+                                        )?;
+                                }
+                                None => {
+                                    logger::info!("Network token requestor reference ID is not available, skipping callback mapper insertion");
+                                }
+                            };
                         };
                     }
                 }
diff --git a/crates/router/src/core/webhooks.rs b/crates/router/src/core/webhooks.rs
index 298cf936132..88a3361ec01 100644
--- a/crates/router/src/core/webhooks.rs
+++ b/crates/router/src/core/webhooks.rs
@@ -3,6 +3,8 @@ mod incoming;
 #[cfg(feature = "v2")]
 mod incoming_v2;
 #[cfg(feature = "v1")]
+mod network_tokenization_incoming;
+#[cfg(feature = "v1")]
 mod outgoing;
 #[cfg(feature = "v2")]
 mod outgoing_v2;
@@ -15,7 +17,7 @@ pub mod webhook_events;
 
 #[cfg(feature = "v1")]
 pub(crate) use self::{
-    incoming::incoming_webhooks_wrapper,
+    incoming::{incoming_webhooks_wrapper, network_token_incoming_webhooks_wrapper},
     outgoing::{
         create_event_and_trigger_outgoing_webhook, get_outgoing_webhook_request,
         trigger_webhook_and_raise_event,
diff --git a/crates/router/src/core/webhooks/incoming.rs b/crates/router/src/core/webhooks/incoming.rs
index 9763e63f179..e9c95576469 100644
--- a/crates/router/src/core/webhooks/incoming.rs
+++ b/crates/router/src/core/webhooks/incoming.rs
@@ -7,7 +7,7 @@ use api_models::webhooks::{self, WebhookResponseTracker};
 use common_utils::{
     errors::ReportSwitchExt,
     events::ApiEventsType,
-    ext_traits::AsyncExt,
+    ext_traits::{AsyncExt, ByteSliceExt},
     types::{AmountConvertor, StringMinorUnitForConnector},
 };
 use diesel_models::ConnectorMandateReferenceId;
@@ -28,10 +28,10 @@ use crate::{
     core::{
         api_locking,
         errors::{self, ConnectorErrorExt, CustomResult, RouterResponse, StorageErrorExt},
-        metrics,
+        metrics, payment_methods,
         payments::{self, tokenization},
         refunds, relay, utils as core_utils,
-        webhooks::utils::construct_webhook_router_data,
+        webhooks::{network_tokenization_incoming, utils::construct_webhook_router_data},
     },
     db::StorageInterface,
     events::api_logs::ApiEvent,
@@ -125,6 +125,68 @@ pub async fn incoming_webhooks_wrapper<W: types::OutgoingWebhookType>(
     Ok(application_response)
 }
 
+#[cfg(feature = "v1")]
+pub async fn network_token_incoming_webhooks_wrapper<W: types::OutgoingWebhookType>(
+    flow: &impl router_env::types::FlowMetric,
+    state: SessionState,
+    req: &actix_web::HttpRequest,
+    body: actix_web::web::Bytes,
+) -> RouterResponse<serde_json::Value> {
+    let start_instant = Instant::now();
+
+    let request_details: IncomingWebhookRequestDetails<'_> = IncomingWebhookRequestDetails {
+        method: req.method().clone(),
+        uri: req.uri().clone(),
+        headers: req.headers(),
+        query_params: req.query_string().to_string(),
+        body: &body,
+    };
+
+    let (application_response, webhooks_response_tracker, serialized_req, merchant_id) = Box::pin(
+        network_token_incoming_webhooks_core::<W>(&state, request_details),
+    )
+    .await?;
+
+    logger::info!(incoming_webhook_payload = ?serialized_req);
+
+    let request_duration = Instant::now()
+        .saturating_duration_since(start_instant)
+        .as_millis();
+
+    let request_id = RequestId::extract(req)
+        .await
+        .change_context(errors::ApiErrorResponse::InternalServerError)
+        .attach_printable("Unable to extract request id from request")?;
+    let auth_type = auth::AuthenticationType::NoAuth;
+    let status_code = 200;
+    let api_event = ApiEventsType::NetworkTokenWebhook {
+        payment_method_id: webhooks_response_tracker.get_payment_method_id(),
+    };
+    let response_value = serde_json::to_value(&webhooks_response_tracker)
+        .change_context(errors::ApiErrorResponse::InternalServerError)
+        .attach_printable("Could not convert webhook effect to string")?;
+    let infra = state.infra_components.clone();
+    let api_event = ApiEvent::new(
+        state.tenant.tenant_id.clone(),
+        Some(merchant_id),
+        flow,
+        &request_id,
+        request_duration,
+        status_code,
+        serialized_req,
+        Some(response_value),
+        None,
+        auth_type,
+        None,
+        api_event,
+        req,
+        req.method(),
+        infra,
+    );
+    state.event_handler().log_event(&api_event);
+    Ok(application_response)
+}
+
 #[allow(clippy::too_many_arguments)]
 #[instrument(skip_all)]
 async fn incoming_webhooks_core<W: types::OutgoingWebhookType>(
@@ -598,6 +660,81 @@ fn handle_incoming_webhook_error(
     }
 }
 
+#[instrument(skip_all)]
+#[cfg(feature = "v1")]
+async fn network_token_incoming_webhooks_core<W: types::OutgoingWebhookType>(
+    state: &SessionState,
+    request_details: IncomingWebhookRequestDetails<'_>,
+) -> errors::RouterResult<(
+    services::ApplicationResponse<serde_json::Value>,
+    WebhookResponseTracker,
+    serde_json::Value,
+    common_utils::id_type::MerchantId,
+)> {
+    let serialized_request =
+        network_tokenization_incoming::get_network_token_resource_object(&request_details)
+            .change_context(errors::ApiErrorResponse::InternalServerError)
+            .attach_printable("Network Token Requestor Webhook deserialization failed")?
+            .masked_serialize()
+            .change_context(errors::ApiErrorResponse::InternalServerError)
+            .attach_printable("Could not convert webhook effect to string")?;
+
+    let network_tokenization_service = &state
+        .conf
+        .network_tokenization_service
+        .as_ref()
+        .ok_or(errors::NetworkTokenizationError::NetworkTokenizationServiceNotConfigured)
+        .change_context(errors::ApiErrorResponse::InternalServerError)
+        .attach_printable("Network Tokenization Service not configured")?;
+
+    //source verification
+    network_tokenization_incoming::Authorization::new(request_details.headers.get("Authorization"))
+        .verify_webhook_source(network_tokenization_service.get_inner())
+        .await?;
+
+    let response: network_tokenization_incoming::NetworkTokenWebhookResponse = request_details
+        .body
+        .parse_struct("NetworkTokenWebhookResponse")
+        .change_context(errors::ApiErrorResponse::WebhookUnprocessableEntity)?;
+
+    let (merchant_id, payment_method_id, _customer_id) = response
+        .fetch_merchant_id_payment_method_id_customer_id_from_callback_mapper(state)
+        .await?;
+
+    metrics::WEBHOOK_SOURCE_VERIFIED_COUNT.add(
+        1,
+        router_env::metric_attributes!((MERCHANT_ID, merchant_id.clone())),
+    );
+
+    let merchant_context =
+        network_tokenization_incoming::fetch_merchant_account_for_network_token_webhooks(
+            state,
+            &merchant_id,
+        )
+        .await?;
+    let payment_method =
+        network_tokenization_incoming::fetch_payment_method_for_network_token_webhooks(
+            state,
+            merchant_context.get_merchant_account(),
+            merchant_context.get_merchant_key_store(),
+            &payment_method_id,
+        )
+        .await?;
+
+    let response_data = response.get_response_data();
+
+    let webhook_resp_tracker = response_data
+        .update_payment_method(state, &payment_method, &merchant_context)
+        .await?;
+
+    Ok((
+        services::ApplicationResponse::StatusOk,
+        webhook_resp_tracker,
+        serialized_request,
+        merchant_id.clone(),
+    ))
+}
+
 #[allow(clippy::too_many_arguments)]
 #[instrument(skip_all)]
 async fn payments_incoming_webhook_flow(
@@ -1316,7 +1453,7 @@ async fn external_authentication_incoming_webhook_flow(
         authentication_details
             .authentication_value
             .async_map(|auth_val| {
-                crate::core::payment_methods::vault::create_tokenize(
+                payment_methods::vault::create_tokenize(
                     &state,
                     auth_val.expose(),
                     None,
diff --git a/crates/router/src/core/webhooks/network_tokenization_incoming.rs b/crates/router/src/core/webhooks/network_tokenization_incoming.rs
new file mode 100644
index 00000000000..a49ec40cbaa
--- /dev/null
+++ b/crates/router/src/core/webhooks/network_tokenization_incoming.rs
@@ -0,0 +1,463 @@
+use std::str::FromStr;
+
+use ::payment_methods::controller::PaymentMethodsController;
+use api_models::webhooks::WebhookResponseTracker;
+use async_trait::async_trait;
+use common_utils::{
+    crypto::Encryptable,
+    ext_traits::{AsyncExt, ByteSliceExt, ValueExt},
+    id_type,
+};
+use error_stack::{report, ResultExt};
+use http::HeaderValue;
+use masking::{ExposeInterface, Secret};
+use serde::{Deserialize, Serialize};
+
+use crate::{
+    configs::settings,
+    core::{
+        errors::{self, CustomResult, RouterResult, StorageErrorExt},
+        payment_methods::cards,
+    },
+    logger,
+    routes::{app::SessionStateInfo, SessionState},
+    types::{
+        api, domain, payment_methods as pm_types,
+        storage::{self, enums},
+    },
+    utils::{self as helper_utils, ext_traits::OptionExt},
+};
+
+#[derive(Debug, Clone, Deserialize, Serialize)]
+#[serde(untagged)]
+pub enum NetworkTokenWebhookResponse {
+    PanMetadataUpdate(pm_types::PanMetadataUpdateBody),
+    NetworkTokenMetadataUpdate(pm_types::NetworkTokenMetaDataUpdateBody),
+}
+
+impl NetworkTokenWebhookResponse {
+    fn get_network_token_requestor_ref_id(&self) -> String {
+        match self {
+            Self::PanMetadataUpdate(data) => data.card.card_reference.clone(),
+            Self::NetworkTokenMetadataUpdate(data) => data.token.card_reference.clone(),
+        }
+    }
+
+    pub fn get_response_data(self) -> Box<dyn NetworkTokenWebhookResponseExt> {
+        match self {
+            Self::PanMetadataUpdate(data) => Box::new(data),
+            Self::NetworkTokenMetadataUpdate(data) => Box::new(data),
+        }
+    }
+
+    pub async fn fetch_merchant_id_payment_method_id_customer_id_from_callback_mapper(
+        &self,
+        state: &SessionState,
+    ) -> RouterResult<(id_type::MerchantId, String, id_type::CustomerId)> {
+        let network_token_requestor_ref_id = &self.get_network_token_requestor_ref_id();
+
+        let db = &*state.store;
+        let callback_mapper_data = db
+            .find_call_back_mapper_by_id(network_token_requestor_ref_id)
+            .await
+            .change_context(errors::ApiErrorResponse::InternalServerError)
+            .attach_printable("Failed to fetch callback mapper data")?;
+
+        Ok(callback_mapper_data
+            .data
+            .get_network_token_webhook_details())
+    }
+}
+
+pub fn get_network_token_resource_object(
+    request_details: &api::IncomingWebhookRequestDetails<'_>,
+) -> CustomResult<Box<dyn masking::ErasedMaskSerialize>, errors::NetworkTokenizationError> {
+    let response: NetworkTokenWebhookResponse = request_details
+        .body
+        .parse_struct("NetworkTokenWebhookResponse")
+        .change_context(errors::NetworkTokenizationError::ResponseDeserializationFailed)?;
+    Ok(Box::new(response))
+}
+
+#[async_trait]
+pub trait NetworkTokenWebhookResponseExt {
+    fn decrypt_payment_method_data(
+        &self,
+        payment_method: &domain::PaymentMethod,
+    ) -> CustomResult<api::payment_methods::CardDetailFromLocker, errors::ApiErrorResponse>;
+
+    async fn update_payment_method(
+        &self,
+        state: &SessionState,
+        payment_method: &domain::PaymentMethod,
+        merchant_context: &domain::MerchantContext,
+    ) -> CustomResult<WebhookResponseTracker, errors::ApiErrorResponse>;
+}
+
+#[async_trait]
+impl NetworkTokenWebhookResponseExt for pm_types::PanMetadataUpdateBody {
+    fn decrypt_payment_method_data(
+        &self,
+        payment_method: &domain::PaymentMethod,
+    ) -> CustomResult<api::payment_methods::CardDetailFromLocker, errors::ApiErrorResponse> {
+        let decrypted_data = payment_method
+            .payment_method_data
+            .clone()
+            .map(|payment_method_data| payment_method_data.into_inner().expose())
+            .and_then(|val| {
+                val.parse_value::<api::payment_methods::PaymentMethodsData>("PaymentMethodsData")
+                    .map_err(|err| logger::error!(?err, "Failed to parse PaymentMethodsData"))
+                    .ok()
+            })
+            .and_then(|pmd| match pmd {
+                api::payment_methods::PaymentMethodsData::Card(token) => {
+                    Some(api::payment_methods::CardDetailFromLocker::from(token))
+                }
+                _ => None,
+            })
+            .ok_or(errors::ApiErrorResponse::InternalServerError)
+            .attach_printable("Failed to obtain decrypted token object from db")?;
+        Ok(decrypted_data)
+    }
+
+    async fn update_payment_method(
+        &self,
+        state: &SessionState,
+        payment_method: &domain::PaymentMethod,
+        merchant_context: &domain::MerchantContext,
+    ) -> CustomResult<WebhookResponseTracker, errors::ApiErrorResponse> {
+        let decrypted_data = self.decrypt_payment_method_data(payment_method)?;
+        handle_metadata_update(
+            state,
+            &self.card,
+            payment_method
+                .locker_id
+                .clone()
+                .get_required_value("locker_id")
+                .change_context(errors::ApiErrorResponse::InternalServerError)
+                .attach_printable("Locker id is not found for the payment method")?,
+            payment_method,
+            merchant_context,
+            decrypted_data,
+            true,
+        )
+        .await
+    }
+}
+
+#[async_trait]
+impl NetworkTokenWebhookResponseExt for pm_types::NetworkTokenMetaDataUpdateBody {
+    fn decrypt_payment_method_data(
+        &self,
+        payment_method: &domain::PaymentMethod,
+    ) -> CustomResult<api::payment_methods::CardDetailFromLocker, errors::ApiErrorResponse> {
+        let decrypted_data = payment_method
+            .network_token_payment_method_data
+            .clone()
+            .map(|x| x.into_inner().expose())
+            .and_then(|val| {
+                val.parse_value::<api::payment_methods::PaymentMethodsData>("PaymentMethodsData")
+                    .map_err(|err| logger::error!(?err, "Failed to parse PaymentMethodsData"))
+                    .ok()
+            })
+            .and_then(|pmd| match pmd {
+                api::payment_methods::PaymentMethodsData::Card(token) => {
+                    Some(api::payment_methods::CardDetailFromLocker::from(token))
+                }
+                _ => None,
+            })
+            .ok_or(errors::ApiErrorResponse::InternalServerError)
+            .attach_printable("Failed to obtain decrypted token object from db")?;
+        Ok(decrypted_data)
+    }
+
+    async fn update_payment_method(
+        &self,
+        state: &SessionState,
+        payment_method: &domain::PaymentMethod,
+        merchant_context: &domain::MerchantContext,
+    ) -> CustomResult<WebhookResponseTracker, errors::ApiErrorResponse> {
+        let decrypted_data = self.decrypt_payment_method_data(payment_method)?;
+        handle_metadata_update(
+            state,
+            &self.token,
+            payment_method
+                .network_token_locker_id
+                .clone()
+                .get_required_value("locker_id")
+                .change_context(errors::ApiErrorResponse::InternalServerError)
+                .attach_printable("Locker id is not found for the payment method")?,
+            payment_method,
+            merchant_context,
+            decrypted_data,
+            true,
+        )
+        .await
+    }
+}
+
+pub struct Authorization {
+    header: Option<HeaderValue>,
+}
+
+impl Authorization {
+    pub fn new(header: Option<&HeaderValue>) -> Self {
+        Self {
+            header: header.cloned(),
+        }
+    }
+
+    pub async fn verify_webhook_source(
+        self,
+        nt_service: &settings::NetworkTokenizationService,
+    ) -> CustomResult<(), errors::ApiErrorResponse> {
+        let secret = nt_service.webhook_source_verification_key.clone();
+
+        let source_verified = match self.header {
+            Some(authorization_header) => match authorization_header.to_str() {
+                Ok(header_value) => Ok(header_value == secret.expose()),
+                Err(err) => {
+                    logger::error!(?err, "Failed to parse authorization header");
+                    Err(errors::ApiErrorResponse::WebhookAuthenticationFailed)
+                }
+            },
+            None => Ok(false),
+        }?;
+        logger::info!(source_verified=?source_verified);
+
+        helper_utils::when(!source_verified, || {
+            Err(report!(
+                errors::ApiErrorResponse::WebhookAuthenticationFailed
+            ))
+        })?;
+
+        Ok(())
+    }
+}
+
+#[allow(clippy::too_many_arguments)]
+pub async fn handle_metadata_update(
+    state: &SessionState,
+    metadata: &pm_types::NetworkTokenRequestorData,
+    locker_id: String,
+    payment_method: &domain::PaymentMethod,
+    merchant_context: &domain::MerchantContext,
+    decrypted_data: api::payment_methods::CardDetailFromLocker,
+    is_pan_update: bool,
+) -> RouterResult<WebhookResponseTracker> {
+    let merchant_id = merchant_context.get_merchant_account().get_id();
+    let customer_id = &payment_method.customer_id;
+    let payment_method_id = payment_method.get_id().clone();
+    let status = payment_method.status;
+
+    match metadata.is_update_required(decrypted_data) {
+        false => {
+            logger::info!(
+                "No update required for payment method {} for locker_id {}",
+                payment_method.get_id(),
+                locker_id
+            );
+            Ok(WebhookResponseTracker::PaymentMethod {
+                payment_method_id,
+                status,
+            })
+        }
+        true => {
+            let mut card = cards::get_card_from_locker(state, customer_id, merchant_id, &locker_id)
+                .await
+                .change_context(errors::ApiErrorResponse::InternalServerError)
+                .attach_printable("Failed to fetch token information from the locker")?;
+
+            card.card_exp_year = metadata.expiry_year.clone();
+            card.card_exp_month = metadata.expiry_month.clone();
+
+            let card_network = card
+                .card_brand
+                .clone()
+                .map(|card_brand| enums::CardNetwork::from_str(&card_brand))
+                .transpose()
+                .change_context(errors::ApiErrorResponse::InvalidDataValue {
+                    field_name: "card network",
+                })
+                .change_context(errors::ApiErrorResponse::InternalServerError)
+                .attach_printable("Invalid Card Network stored in vault")?;
+
+            let card_data = api::payment_methods::CardDetail::from((card, card_network));
+
+            let payment_method_request: api::payment_methods::PaymentMethodCreate =
+                PaymentMethodCreateWrapper::from((&card_data, payment_method)).get_inner();
+
+            let pm_cards = cards::PmCards {
+                state,
+                merchant_context,
+            };
+
+            pm_cards
+                .delete_card_from_locker(customer_id, merchant_id, &locker_id)
+                .await
+                .change_context(errors::ApiErrorResponse::InternalServerError)
+                .attach_printable("Failed to delete network token")?;
+
+            let (res, _) = pm_cards
+                .add_card_to_locker(payment_method_request, &card_data, customer_id, None)
+                .await
+                .change_context(errors::ApiErrorResponse::InternalServerError)
+                .attach_printable("Failed to add network token")?;
+
+            let pm_details = res.card.as_ref().map(|card| {
+                api::payment_methods::PaymentMethodsData::Card(
+                    api::payment_methods::CardDetailsPaymentMethod::from((card.clone(), None)),
+                )
+            });
+            let key_manager_state = state.into();
+
+            let pm_data_encrypted: Option<Encryptable<Secret<serde_json::Value>>> = pm_details
+                .async_map(|pm_card| {
+                    cards::create_encrypted_data(
+                        &key_manager_state,
+                        merchant_context.get_merchant_key_store(),
+                        pm_card,
+                    )
+                })
+                .await
+                .transpose()
+                .change_context(errors::ApiErrorResponse::InternalServerError)
+                .attach_printable("Unable to encrypt payment method data")?;
+
+            let pm_update = if is_pan_update {
+                storage::PaymentMethodUpdate::AdditionalDataUpdate {
+                    locker_id: Some(res.payment_method_id),
+                    payment_method_data: pm_data_encrypted.map(Into::into),
+                    status: None,
+                    payment_method: None,
+                    payment_method_type: None,
+                    payment_method_issuer: None,
+                    network_token_requestor_reference_id: None,
+                    network_token_locker_id: None,
+                    network_token_payment_method_data: None,
+                }
+            } else {
+                storage::PaymentMethodUpdate::AdditionalDataUpdate {
+                    locker_id: None,
+                    payment_method_data: None,
+                    status: None,
+                    payment_method: None,
+                    payment_method_type: None,
+                    payment_method_issuer: None,
+                    network_token_requestor_reference_id: None,
+                    network_token_locker_id: Some(res.payment_method_id),
+                    network_token_payment_method_data: pm_data_encrypted.map(Into::into),
+                }
+            };
+            let db = &*state.store;
+
+            db.update_payment_method(
+                &key_manager_state,
+                merchant_context.get_merchant_key_store(),
+                payment_method.clone(),
+                pm_update,
+                merchant_context.get_merchant_account().storage_scheme,
+            )
+            .await
+            .change_context(errors::ApiErrorResponse::InternalServerError)
+            .attach_printable("Failed to update the payment method")?;
+
+            Ok(WebhookResponseTracker::PaymentMethod {
+                payment_method_id,
+                status,
+            })
+        }
+    }
+}
+
+pub struct PaymentMethodCreateWrapper(pub api::payment_methods::PaymentMethodCreate);
+
+impl From<(&api::payment_methods::CardDetail, &domain::PaymentMethod)>
+    for PaymentMethodCreateWrapper
+{
+    fn from(
+        (data, payment_method): (&api::payment_methods::CardDetail, &domain::PaymentMethod),
+    ) -> Self {
+        Self(api::payment_methods::PaymentMethodCreate {
+            customer_id: Some(payment_method.customer_id.clone()),
+            payment_method: payment_method.payment_method,
+            payment_method_type: payment_method.payment_method_type,
+            payment_method_issuer: payment_method.payment_method_issuer.clone(),
+            payment_method_issuer_code: payment_method.payment_method_issuer_code,
+            metadata: payment_method.metadata.clone(),
+            payment_method_data: None,
+            connector_mandate_details: None,
+            client_secret: None,
+            billing: None,
+            card: Some(data.clone()),
+            card_network: data
+                .card_network
+                .clone()
+                .map(|card_network| card_network.to_string()),
+            bank_transfer: None,
+            wallet: None,
+            network_transaction_id: payment_method.network_transaction_id.clone(),
+        })
+    }
+}
+
+impl PaymentMethodCreateWrapper {
+    fn get_inner(self) -> api::payment_methods::PaymentMethodCreate {
+        self.0
+    }
+}
+
+pub async fn fetch_merchant_account_for_network_token_webhooks(
+    state: &SessionState,
+    merchant_id: &id_type::MerchantId,
+) -> RouterResult<domain::MerchantContext> {
+    let db = &*state.store;
+    let key_manager_state = &(state).into();
+
+    let key_store = state
+        .store()
+        .get_merchant_key_store_by_merchant_id(
+            key_manager_state,
+            merchant_id,
+            &state.store().get_master_key().to_vec().into(),
+        )
+        .await
+        .change_context(errors::ApiErrorResponse::Unauthorized)
+        .attach_printable("Failed to fetch merchant key store for the merchant id")?;
+
+    let merchant_account = db
+        .find_merchant_account_by_merchant_id(key_manager_state, merchant_id, &key_store)
+        .await
+        .change_context(errors::ApiErrorResponse::InternalServerError)
+        .attach_printable("Failed to fetch merchant account for the merchant id")?;
+
+    let merchant_context = domain::MerchantContext::NormalMerchant(Box::new(domain::Context(
+        merchant_account.clone(),
+        key_store,
+    )));
+
+    Ok(merchant_context)
+}
+
+pub async fn fetch_payment_method_for_network_token_webhooks(
+    state: &SessionState,
+    merchant_account: &domain::MerchantAccount,
+    key_store: &domain::MerchantKeyStore,
+    payment_method_id: &str,
+) -> RouterResult<domain::PaymentMethod> {
+    let db = &*state.store;
+    let key_manager_state = &(state).into();
+
+    let payment_method = db
+        .find_payment_method(
+            key_manager_state,
+            key_store,
+            payment_method_id,
+            merchant_account.storage_scheme,
+        )
+        .await
+        .to_not_found_response(errors::ApiErrorResponse::WebhookResourceNotFound)
+        .attach_printable("Failed to fetch the payment method")?;
+
+    Ok(payment_method)
+}
diff --git a/crates/router/src/db.rs b/crates/router/src/db.rs
index d77e1926260..59c31d4b2b8 100644
--- a/crates/router/src/db.rs
+++ b/crates/router/src/db.rs
@@ -142,6 +142,7 @@ pub trait StorageInterface:
     + user::theme::ThemeInterface
     + payment_method_session::PaymentMethodsSessionInterface
     + tokenization::TokenizationInterface
+    + callback_mapper::CallbackMapperInterface
     + 'static
 {
     fn get_scheduler_db(&self) -> Box<dyn scheduler::SchedulerInterface>;
diff --git a/crates/router/src/db/callback_mapper.rs b/crates/router/src/db/callback_mapper.rs
index 0697f41bdac..70e9bd30ccc 100644
--- a/crates/router/src/db/callback_mapper.rs
+++ b/crates/router/src/db/callback_mapper.rs
@@ -1,7 +1,7 @@
 use error_stack::report;
 use hyperswitch_domain_models::callback_mapper as domain;
 use router_env::{instrument, tracing};
-use storage_impl::DataModelExt;
+use storage_impl::{DataModelExt, MockDb};
 
 use super::Store;
 use crate::{
@@ -51,3 +51,22 @@ impl CallbackMapperInterface for Store {
             .map(domain::CallbackMapper::from_storage_model)
     }
 }
+
+#[async_trait::async_trait]
+impl CallbackMapperInterface for MockDb {
+    #[instrument(skip_all)]
+    async fn insert_call_back_mapper(
+        &self,
+        _call_back_mapper: domain::CallbackMapper,
+    ) -> CustomResult<domain::CallbackMapper, errors::StorageError> {
+        Err(errors::StorageError::MockDbError)?
+    }
+
+    #[instrument(skip_all)]
+    async fn find_call_back_mapper_by_id(
+        &self,
+        _id: &str,
+    ) -> CustomResult<domain::CallbackMapper, errors::StorageError> {
+        Err(errors::StorageError::MockDbError)?
+    }
+}
diff --git a/crates/router/src/routes/app.rs b/crates/router/src/routes/app.rs
index f1c2def317b..53e31ae1e49 100644
--- a/crates/router/src/routes/app.rs
+++ b/crates/router/src/routes/app.rs
@@ -1714,6 +1714,24 @@ impl Webhooks {
         #[allow(unused_mut)]
         let mut route = web::scope("/webhooks")
             .app_data(web::Data::new(config))
+            .service(
+                web::resource("/network_token_requestor/ref")
+                    .route(
+                        web::post().to(receive_network_token_requestor_incoming_webhook::<
+                            webhook_type::OutgoingWebhook,
+                        >),
+                    )
+                    .route(
+                        web::get().to(receive_network_token_requestor_incoming_webhook::<
+                            webhook_type::OutgoingWebhook,
+                        >),
+                    )
+                    .route(
+                        web::put().to(receive_network_token_requestor_incoming_webhook::<
+                            webhook_type::OutgoingWebhook,
+                        >),
+                    ),
+            )
             .service(
                 web::resource("/{merchant_id}/{connector_id_or_name}")
                     .route(
diff --git a/crates/router/src/routes/lock_utils.rs b/crates/router/src/routes/lock_utils.rs
index c1f3533ef1f..fa616a3da3e 100644
--- a/crates/router/src/routes/lock_utils.rs
+++ b/crates/router/src/routes/lock_utils.rs
@@ -192,7 +192,8 @@ impl From<Flow> for ApiIdentifier {
             | Flow::WebhookEventInitialDeliveryAttemptList
             | Flow::WebhookEventDeliveryAttemptList
             | Flow::WebhookEventDeliveryRetry
-            | Flow::RecoveryIncomingWebhookReceive => Self::Webhooks,
+            | Flow::RecoveryIncomingWebhookReceive
+            | Flow::IncomingNetworkTokenWebhookReceive => Self::Webhooks,
 
             Flow::ApiKeyCreate
             | Flow::ApiKeyRetrieve
diff --git a/crates/router/src/routes/webhooks.rs b/crates/router/src/routes/webhooks.rs
index d19f48b62f0..41188288eab 100644
--- a/crates/router/src/routes/webhooks.rs
+++ b/crates/router/src/routes/webhooks.rs
@@ -179,3 +179,32 @@ pub async fn receive_incoming_webhook<W: types::OutgoingWebhookType>(
     ))
     .await
 }
+
+#[cfg(feature = "v1")]
+#[instrument(skip_all, fields(flow = ?Flow::IncomingNetworkTokenWebhookReceive))]
+pub async fn receive_network_token_requestor_incoming_webhook<W: types::OutgoingWebhookType>(
+    state: web::Data<AppState>,
+    req: HttpRequest,
+    body: web::Bytes,
+    _path: web::Path<String>,
+) -> impl Responder {
+    let flow = Flow::IncomingNetworkTokenWebhookReceive;
+
+    Box::pin(api::server_wrap(
+        flow.clone(),
+        state,
+        &req,
+        (),
+        |state, _: (), _, _| {
+            webhooks::network_token_incoming_webhooks_wrapper::<W>(
+                &flow,
+                state.to_owned(),
+                &req,
+                body.clone(),
+            )
+        },
+        &auth::NoAuth,
+        api_locking::LockAction::NotApplicable,
+    ))
+    .await
+}
diff --git a/crates/router/src/types/domain.rs b/crates/router/src/types/domain.rs
index 3e94375b5e6..63c27e43008 100644
--- a/crates/router/src/types/domain.rs
+++ b/crates/router/src/types/domain.rs
@@ -19,7 +19,7 @@ mod customers {
     pub use hyperswitch_domain_models::customer::*;
 }
 
-mod callback_mapper {
+pub mod callback_mapper {
     pub use hyperswitch_domain_models::callback_mapper::CallbackMapper;
 }
 
diff --git a/crates/router/src/types/payment_methods.rs b/crates/router/src/types/payment_methods.rs
index 29814ddcef4..0d205f33e8f 100644
--- a/crates/router/src/types/payment_methods.rs
+++ b/crates/router/src/types/payment_methods.rs
@@ -15,10 +15,11 @@ use hyperswitch_domain_models::payment_method_data::NetworkTokenDetails;
 use masking::Secret;
 use serde::{Deserialize, Serialize};
 
+use crate::types::api;
 #[cfg(feature = "v2")]
 use crate::{
     consts,
-    types::{api, domain, storage},
+    types::{domain, storage},
 };
 
 #[cfg(feature = "v2")]
@@ -357,3 +358,33 @@ pub struct CheckTokenStatusResponsePayload {
 pub struct CheckTokenStatusResponse {
     pub payload: CheckTokenStatusResponsePayload,
 }
+
+#[derive(Debug, Clone, Deserialize, Serialize)]
+pub struct NetworkTokenRequestorData {
+    pub card_reference: String,
+    pub customer_id: String,
+    pub expiry_year: Secret<String>,
+    pub expiry_month: Secret<String>,
+}
+
+impl NetworkTokenRequestorData {
+    pub fn is_update_required(
+        &self,
+        data_stored_in_vault: api::payment_methods::CardDetailFromLocker,
+    ) -> bool {
+        //if the expiry year and month in the vault are not the same as the ones in the requestor data,
+        //then we need to update the vault data with the updated expiry year and month.
+        !((data_stored_in_vault.expiry_year.unwrap_or_default() == self.expiry_year)
+            && (data_stored_in_vault.expiry_month.unwrap_or_default() == self.expiry_month))
+    }
+}
+
+#[derive(Debug, Clone, Deserialize, Serialize)]
+pub struct NetworkTokenMetaDataUpdateBody {
+    pub token: NetworkTokenRequestorData,
+}
+
+#[derive(Debug, Clone, Deserialize, Serialize)]
+pub struct PanMetadataUpdateBody {
+    pub card: NetworkTokenRequestorData,
+}
diff --git a/crates/router_env/src/logger/types.rs b/crates/router_env/src/logger/types.rs
index 4e0bdab3ce6..d395cd1f6da 100644
--- a/crates/router_env/src/logger/types.rs
+++ b/crates/router_env/src/logger/types.rs
@@ -606,6 +606,8 @@ pub enum Flow {
     ProfileAcquirerUpdate,
     /// ThreeDs Decision Rule Execute flow
     ThreeDsDecisionRuleExecute,
+    /// Incoming Network Token Webhook Receive
+    IncomingNetworkTokenWebhookReceive,
 }
 
 /// Trait for providing generic behaviour to flow metric
diff --git a/crates/storage_impl/src/callback_mapper.rs b/crates/storage_impl/src/callback_mapper.rs
index 186f2b7f927..4f22dd079e9 100644
--- a/crates/storage_impl/src/callback_mapper.rs
+++ b/crates/storage_impl/src/callback_mapper.rs
@@ -9,7 +9,7 @@ impl DataModelExt for CallbackMapper {
     fn to_storage_model(self) -> Self::StorageModel {
         DieselCallbackMapper {
             id: self.id,
-            type_: self.type_,
+            type_: self.callback_mapper_id_type,
             data: self.data,
             created_at: self.created_at,
             last_modified_at: self.last_modified_at,
@@ -19,7 +19,7 @@ impl DataModelExt for CallbackMapper {
     fn from_storage_model(storage_model: Self::StorageModel) -> Self {
         Self {
             id: storage_model.id,
-            type_: storage_model.type_,
+            callback_mapper_id_type: storage_model.type_,
             data: storage_model.data,
             created_at: storage_model.created_at,
             last_modified_at: storage_model.last_modified_at,

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

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

## Description
<!-- Describe your changes in detail -->
Add webhooks for network tokenization
- added support to process webhooks from token service.
- verify webhooks updates the pan and network token metadata

### 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).
-->


## 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)?
-->
This cannot be tested unless we get real-time webhooks from CardNetwork.


## 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


<!-- This is an auto-generated comment: release notes by coderabbit.ai -->
## Summary by CodeRabbit

- **New Features**
  - Introduced support for network tokenization webhooks, including a new dedicated endpoint for network token requestor reference webhooks.
  - Added new webhook event types and metadata update flows for payment methods linked to network tokens.
  - Enhanced validation and configuration options for network tokenization services, including webhook source verification.
  - Exposed new data structures and API responses for handling network token and card metadata updates.
  - Added callback mapper integration to link webhook callbacks with merchant, payment method, and customer data.

- **Bug Fixes**
  - Improved validation to ensure all required secrets, including webhook verification keys, are present for network tokenization services.

- **Documentation**
  - Expanded public API surface and type exports to support new webhook and callback mapping features.
<!-- end of auto-generated comment: release notes by coderabbit.ai -->