feat(webhooks): store request and response payloads in events table (#4029)

This commit is contained in:
Sanchith Hegde
2024-03-14 12:30:37 +05:30
committed by GitHub
parent ad17cc7383
commit fd67a6c225
32 changed files with 1267 additions and 644 deletions

View File

@ -1,17 +1,23 @@
use api_models::webhooks;
use common_utils::{crypto::SignMessage, ext_traits::Encode};
use error_stack::ResultExt;
use masking::Secret;
use serde::Serialize;
use crate::{core::errors, headers, services::request::Maskable, types::storage::enums};
pub struct OutgoingWebhookPayloadWithSignature {
pub payload: Secret<String>,
pub signature: Option<String>,
}
pub trait OutgoingWebhookType:
Serialize + From<webhooks::OutgoingWebhook> + Sync + Send + std::fmt::Debug + 'static
{
fn get_outgoing_webhooks_signature(
&self,
payment_response_hash_key: Option<String>,
) -> errors::CustomResult<Option<String>, errors::WebhooksFlowError>;
payment_response_hash_key: Option<impl AsRef<[u8]>>,
) -> errors::CustomResult<OutgoingWebhookPayloadWithSignature, errors::WebhooksFlowError>;
fn add_webhook_header(header: &mut Vec<(String, Maskable<String>)>, signature: String);
}
@ -19,26 +25,32 @@ pub trait OutgoingWebhookType:
impl OutgoingWebhookType for webhooks::OutgoingWebhook {
fn get_outgoing_webhooks_signature(
&self,
payment_response_hash_key: Option<String>,
) -> errors::CustomResult<Option<String>, errors::WebhooksFlowError> {
payment_response_hash_key: Option<impl AsRef<[u8]>>,
) -> errors::CustomResult<OutgoingWebhookPayloadWithSignature, errors::WebhooksFlowError> {
let webhook_signature_payload = self
.encode_to_string_of_json()
.change_context(errors::WebhooksFlowError::OutgoingWebhookEncodingFailed)
.attach_printable("failed encoding outgoing webhook payload")?;
Ok(payment_response_hash_key
let signature = payment_response_hash_key
.map(|key| {
common_utils::crypto::HmacSha512::sign_message(
&common_utils::crypto::HmacSha512,
key.as_bytes(),
key.as_ref(),
webhook_signature_payload.as_bytes(),
)
})
.transpose()
.change_context(errors::WebhooksFlowError::OutgoingWebhookSigningFailed)
.attach_printable("Failed to sign the message")?
.map(hex::encode))
.map(hex::encode);
Ok(OutgoingWebhookPayloadWithSignature {
payload: webhook_signature_payload.into(),
signature,
})
}
fn add_webhook_header(header: &mut Vec<(String, Maskable<String>)>, signature: String) {
header.push((headers::X_WEBHOOK_SIGNATURE.to_string(), signature.into()))
}
@ -58,4 +70,18 @@ pub(crate) struct OutgoingWebhookTrackingData {
pub(crate) event_class: enums::EventClass,
pub(crate) primary_object_id: String,
pub(crate) primary_object_type: enums::EventObjectType,
pub(crate) initial_attempt_id: Option<String>,
}
#[derive(Debug, serde::Serialize, serde::Deserialize)]
pub(crate) struct OutgoingWebhookRequestContent {
pub(crate) payload: Secret<String>,
pub(crate) headers: Vec<(String, Secret<String>)>,
}
#[derive(Debug, serde::Serialize, serde::Deserialize)]
pub(crate) struct OutgoingWebhookResponseContent {
pub(crate) payload: Secret<String>,
pub(crate) headers: Vec<(String, Secret<String>)>,
pub(crate) status_code: u16,
}

View File

@ -120,3 +120,27 @@ pub async fn construct_webhook_router_data<'a>(
};
Ok(router_data)
}
#[inline]
pub(crate) fn get_idempotent_event_id(
primary_object_id: &str,
event_type: crate::types::storage::enums::EventType,
delivery_attempt: super::types::WebhookDeliveryAttempt,
) -> String {
use super::types::WebhookDeliveryAttempt;
const EVENT_ID_SUFFIX_LENGTH: usize = 8;
let common_prefix = format!("{primary_object_id}_{event_type}");
match delivery_attempt {
WebhookDeliveryAttempt::InitialAttempt => common_prefix,
WebhookDeliveryAttempt::AutomaticRetry => {
common_utils::generate_id(EVENT_ID_SUFFIX_LENGTH, &common_prefix)
}
}
}
#[inline]
pub(crate) fn generate_event_id() -> String {
common_utils::generate_time_ordered_id("evt")
}