Skip to content

feat(core): Adding integration for webhooks through UCS #8814

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 20 commits into from
Aug 8, 2025

Conversation

AmitsinghTanwar007
Copy link
Contributor

@AmitsinghTanwar007 AmitsinghTanwar007 commented Aug 1, 2025

Type of Change

  • Bugfix
  • New feature
  • Enhancement
  • Refactoring
  • Dependency updates
  • Documentation
  • CI/CD

Description

Added webhooks integration support in hyperswitch so that for webhooks hyperswitch can call UCS and get the response.

Closes #8880

Additional Changes

  • This PR modifies the API contract
  • This PR modifies the database schema
  • This PR modifies application configuration/environment variables

Motivation and Context

How did you test it?

curl --location 'http://localhost:8080/webhooks/merchant_1753974582/mca_lwqSM0js79MQm1DGwYRj' \
--header 'Content-Type: application/json' \
--header 'X-ANET-Signature: sha512=38b0bc1ea66b14793e39cd58e93d37b799a507442d0dd8d37443fa95dec58e57da6db4742636fea31201c48e57a66e73a308a2e5a5c6bb831e4e39fe2227c00f' \
--header 'api-key: dev_gkgtNkEyXov857WXSuWWiduf9a2PnTLd78j7ZVUheZ86M7nroCye8G3BEgqcL5SH' \
--data '{
    "notificationId": "550e8400-e29b-41d4-a716-446655440000",
    "eventType": "net.authorize.payment.authorization.created",
    "eventDate": "2023-12-01T12:00:00Z",
    "webhookId": "webhook_123",
    "payload": {
      "responseCode": 1,
      "entityName": "transaction",
      "id": "120068558980",
      "authAmount": 6540,
      "merchantReferenceId": "REF123",
      "authCode": "ABC123",
      "messageText": "This transaction has been approved.",
      "avsResponse": "Y",
      "cvvResponse": "M"
    }
  }'

Checklist

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

img of hyperswitch
Screenshot 2025-08-07 at 9 32 55 AM

img of UCS
Screenshot 2025-08-07 at 9 30 58 AM

Steps to test webhooks
✅ 1. Create a Sandbox Account on Authorize.Net
Go to: https://developer.authorize.net/hello_world/sandbox/

Sign up for a sandbox account.

After registration, note your:

API Login ID

Transaction Key

(Optional: Public Client Key if needed)

✅ 2. Generate the Signature Key
Log in to the Authorize.Net Sandbox

Navigate to:
Account → Settings → API Credentials & Keys

Under Signature Key, click New Signature Key → click Submit

Copy and save the Signature Key safely — you’ll use it to verify webhooks.

✅ 3. Enable Webhooks in Authorize.Net
In your sandbox account, go to:
Account → Settings → Webhooks

Click Add Endpoint

Endpoint URL:
https://f9475498739857e.ngrok-free.app/webhooks/merchant_id/mca_of_merchant

Event Types: Select the events you want (e.g., net.authorize.payment.authcapture.created)

Save the webhook.

✅ 4. Configure Hyperswitch Merchant with Signature & Credentials
While creating a merchant in Hyperswitch, add the following:
authentication creds
BodyKey
api_key = API Login ID (from step 1)

key1 = Transaction Key

and below you file find a field named merchant secret where you will put the signature
Signature Key (from step 2)

This allows Hyperswitch to authenticate with Authorize.Net and validate incoming webhooks.

✅ 5. Trigger a Payment to Receive Webhooks
Use Hyperswitch to make a test payment through the merchant you configured.

If everything is set up:

Authorize.Net will send a webhook to your endpoint (ngrok URL).

Your backend should receive and verify the webhook using the signature key.

✅ Optional: Use ngrok to Test Webhooks Locally
If running locally:
ngrok http 8080
Use the https://xxxx.ngrok-free.app URL as the webhook URL in step 3.

@AmitsinghTanwar007 AmitsinghTanwar007 requested review from a team as code owners August 1, 2025 07:07
@AmitsinghTanwar007 AmitsinghTanwar007 added A-core Area: Core flows A-webhooks Area: Webhook flows labels Aug 1, 2025
Copy link

semanticdiff-com bot commented Aug 1, 2025

};

// Make UCS call
if let Some(ucs_client) = &state.grpc_client.unified_connector_service_client {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

At the function start itself have this check

} else {
// UCS client not available but UCS is configured
// We don't fall back to direct connector processing when UCS is configured
router_env::logger::error!(
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Attach printable already server the logging purpose, so this log will be redundant one

Err(err) => {
// When UCS is configured, we don't fall back to direct connector processing
// since the goal is to remove direct connector code in the future
router_env::logger::error!(
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Attach printable already server the logging purpose, so this log will be redundant one


/// Decide whether to use UCS for webhook processing based on configuration
/// This mirrors the pattern used in payment flows for UCS decision making
pub async fn decide_webhook_ucs_processing(
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this is function really required it is just a wrapper on top should_call_unified_connector_service_for_webhooks this, we can simply call should_call_unified_connector_service_for_webhooks this itself where decide_webhook_ucs_processing is called

connector_id: &str,
) -> Result<
PaymentServiceTransformRequest,
error_stack::Report<crate::core::errors::ApiErrorResponse>,
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
error_stack::Report<crate::core::errors::ApiErrorResponse>,
error_stack::Report<errors::ApiErrorResponse>,

error_stack::Report<crate::core::errors::ApiErrorResponse>,
> {
let request_details_grpc = payments_grpc::RequestDetails::foreign_try_from(request_details)
.change_context(crate::core::errors::ApiErrorResponse::InternalServerError)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
.change_context(crate::core::errors::ApiErrorResponse::InternalServerError)
.change_context(errors::ApiErrorResponse::InternalServerError)

let mut event_object: Box<dyn masking::ErasedMaskSerialize> = Box::new(serde_json::Value::Null);

let webhook_effect =
if process_webhook_further && !matches!(flow_type, api::WebhookFlow::ReturnResponse) {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

the matches can move to where process_webhook_further is constructed

response
}
Err(error) => {
return handle_incoming_webhook_error(
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

avoid early return here, only for validation at top return can be there, that also using Result with ? operator can be avoid, don't follow return pattern based code, other places also change this pattern

{
Ok(response) => {
// Extract event object for serialization
event_object = if let Some(transform_data) = webhook_transform_data {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Avoid deep nested if else structure, have handler function to handle response handling, and for option use match and avoid using if let some syntax, chaining and writing in idiomatic way follow those.

jarnura
jarnura previously approved these changes Aug 6, 2025

/// High-level abstraction for transforming webhooks via UCS
/// This function encapsulates all UCS communication and request building logic
pub async fn transform_webhook_via_ucs(
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Where is this function called? I cannot see it being referenced anywhere.

hrithikesh026
hrithikesh026 previously approved these changes Aug 6, 2025
hrithikesh026
hrithikesh026 previously approved these changes Aug 6, 2025
jarnura
jarnura previously approved these changes Aug 6, 2025
@SanchithHegde
Copy link
Member

@AmitsinghTanwar007 Can you please add testing information in the PR description?

@likhinbopanna likhinbopanna enabled auto-merge August 8, 2025 12:52
@likhinbopanna likhinbopanna added this pull request to the merge queue Aug 8, 2025
Merged via the queue into main with commit 06dc66c Aug 8, 2025
17 of 22 checks passed
@likhinbopanna likhinbopanna deleted the ucs-webhook-integration branch August 8, 2025 14:35
pixincreate added a commit that referenced this pull request Aug 8, 2025
…ordea-sepa

* 'main' of github.com:juspay/hyperswitch:
  feat(core): Adding integration for webhooks through UCS (#8814)
  refactor(euclid): refactor logs for evaluation of equality for dynamic routing evaluate response (#8834)
  feat(connector): [SIFT] add Connector Template Code  (#8488)
  feat(router): Add tokenization support for proxy and update the route for proxy (#8530)
  fix(ci): Fix Spell Check For CI Pull Request (#8857)
  feat(checkbook_io): connector integrate ACH (#8730)
  fix(connector): Change Refund Reason Type in Adyen (#8849)
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
A-core Area: Core flows A-webhooks Area: Webhook flows
Projects
None yet
Development

Successfully merging this pull request may close these issues.

[FEATURE]: Add support for Webhooks through UCS
5 participants