hyperswitch
hyperswitch copied to clipboard
feat(webhooks): Provide outgoing webhook support for revenue recovery
Type of Change
- [ ] Bugfix
- [x] New feature
- [ ] Enhancement
- [ ] Refactoring
- [ ] Dependency updates
- [ ] Documentation
- [ ] CI/CD
Description
This PR enables outgoing webhook support for revenue recovery.
When the retries get succeeded that are done using the revenue recovery system, an outgoing webhook needs to be triggered to the url in business profile. This pr adds that support.
Additional Changes
- [x] 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?
- create a billing profile with webhook.site url to monitor the webhooks.
- Create an payment mca using this
curl --location 'http://localhost:8080/v2/connector-accounts' \
--header 'Content-Type: application/json' \
--header 'Accept: application/json' \
--header 'x-merchant-id: {{merchant-id}}'
--header 'x-profile-id: {{profile-id}}' \
--header 'Authorization: {{api-key}}'
--header 'api-key:{{api-key}}' \
--data '{
"connector_type": "payment_processor",
"connector_name": "stripe",
"connector_account_details": {
"auth_type": "HeaderKey",
"api_key": "{{api_key}}"
},
"payment_methods_enabled": [
{
"payment_method_type": "card",
"payment_method_subtypes": [
{
"payment_method_subtype": "credit",
"payment_experience": null,
"card_networks": [
"Visa",
"Mastercard"
],
"accepted_currencies": null,
"accepted_countries": null,
"minimum_amount": -1,
"maximum_amount": 68607706,
"recurring_enabled": true,
"installment_payment_enabled": true
},
{
"payment_method_subtype": "debit",
"payment_experience": null,
"card_networks": [
"Visa",
"Mastercard"
],
"accepted_currencies": null,
"accepted_countries": null,
"minimum_amount": -1,
"maximum_amount": 68607706,
"recurring_enabled": true,
"installment_payment_enabled": true
},
{
"payment_method_subtype": "card",
"payment_experience": null,
"card_networks": [
"Visa",
"Mastercard"
],
"accepted_currencies": null,
"accepted_countries": null,
"minimum_amount": -1,
"maximum_amount": 68607706,
"recurring_enabled": true,
"installment_payment_enabled": true
}
]
}
],
"frm_configs": null,
"connector_webhook_details": {
"merchant_secret": ""
},
"metadata": {
"report_group": "Hello",
"merchant_config_currency": "USD"
},
"profile_id": "{{profile_id}}"
}'
- create an billing mca
curl --location 'http://localhost:8080/v2/connector-accounts' \
--header 'Content-Type: application/json' \
--header 'Accept: application/json' \
--header 'x-merchant-id: {{merchant-id}}'
--header 'x-profile-id: {{profile-id}}' \
--header 'Authorization: {{api-key}}'
--header 'api-key:{{api-key}}' \
--data '{
"connector_type": "billing_processor",
"connector_name": "custombilling",
"connector_account_details": {
"auth_type": "NoKey"
},
"feature_metadata" : {
"revenue_recovery" : {
"max_retry_count" : 9,
"billing_connector_retry_threshold" : 0,
"billing_account_reference" :{
"{{payment_mca}}" : "{{payment_mca}}"
},
"switch_payment_method_config" : {
"retry_threshold" : 0,
"time_threshold_after_creation": 0
}
}
},
"profile_id": "{{profile_id}}"
}'
- switch revenue recovery config to cascading
curl --location --request PUT 'http://localhost:8080/v2/profiles/pro_DD6ZRORqBtEwR3ITjseM' \
--header 'x-merchant-id: {{erchant-id}}' \
--header 'Authorization: {{api-key}}' \
--header 'Content-Type: application/json' \
--header 'api-key: {{api-key}}' \
--data '{
"revenue_recovery_retry_algorithm_type": "cascading"
} '
- Go to stripe dashboard and create a customer and attach a card for that customer
- Hit this recovery api with that customer_id and payment_method_id
curl --location 'http://localhost:8080/v2/payments/recovery' \
--header 'Authorization: {{api-key}}'
--header 'x-profile-id: {{profile-id}}
--header 'x-merchant-id: {{merchant-id}}'
--header 'Content-Type: application/json' \
--header 'api-key:{{api_key}}' \
--data '{
"amount_details": {
"order_amount": 2250,
"currency": "USD"
},
"merchant_reference_id": "test_ffhgfewf3476f5",
"connector_transaction_id": "1831984",
"connector_customer_id": "{{customer_id_from_stripe}}",
"error": {
"code": "110",
"message": "Insufficient Funds"
},
"billing": {
"address": {
"state": "CA",
"country": "US"
}
},
"payment_method_type": "card",
"payment_method_sub_type": "credit",
"payment_method_data": {
"primary_processor_payment_method_token": "{{payment_method_id_from_stripe}}",
"additional_payment_method_info": {
"card_exp_month": "12",
"card_exp_year": "25",
"last_four_digits": 1997,
"card_network": "Visa",
"card_issuer": "Wells Fargo NA",
"card_type": "credit"
}
},
"billing_started_at": "2024-05-29T08:10:58Z",
"transaction_created_at": "2024-05-29T08:10:58Z",
"attempt_status": "failure",
"action": "schedule_failed_payment",
"billing_merchant_connector_id": "{{billing_mca}}
"payment_merchant_connector_id": "{{payment_mca}}"
}'
- There will be a process tracker entry now and wait till it get triggered. Based on the test card if we mentioned a successful card's payment method id then we will get a webhook to webhook.site url.
Sample screen shot and sample webhook body are mentioned below
Sample body of the webhook
{
"merchant_id": "cloth_seller_I7nS4iwZnxRDJtGfiNTy",
"event_id": "evt_019925c838c877c282d3eaf5b86e9036",
"event_type": "payment_succeeded",
"content": {
"type": "payment_details",
"object": {
"id": "12345_pay_019925c5959a79418301a9460b92edb1",
"status": "succeeded",
"amount": {
"order_amount": 2250,
"currency": "USD",
"shipping_cost": null,
"order_tax_amount": null,
"external_tax_calculation": "skip",
"surcharge_calculation": "skip",
"surcharge_amount": null,
"tax_on_surcharge": null,
"net_amount": 2250,
"amount_to_capture": null,
"amount_capturable": 0,
"amount_captured": 2250
},
"customer_id": null,
"connector": "stripe",
"created": "2025-09-07T20:02:09.947Z",
"modified_at": "2025-09-07T20:05:02.777Z",
"payment_method_data": {
"billing": null
},
"payment_method_type": "card",
"payment_method_subtype": "credit",
"connector_transaction_id": "pi_3S4opd2KXBHSNjod0tD34Dmw",
"connector_reference_id": "pi_3S4opd2KXBHSNjod0tD34Dmw",
"merchant_connector_id": "mca_MbQwWzi4tFItmgYAmshC",
"browser_info": null,
"error": null,
"shipping": null,
"billing": null,
"attempts": null,
"connector_token_details": {
"token": "pm_1S4olu2KXBHSNjodBbiedqY9",
"connector_token_request_reference_id": null
},
"payment_method_id": null,
"next_action": null,
"return_url": "https://google.com/success",
"authentication_type": "no_three_ds",
"authentication_type_applied": "no_three_ds",
"is_iframe_redirection_enabled": null,
"merchant_reference_id": "test_ffhgfewf3476f5",
"raw_connector_response": null,
"feature_metadata": {
"redirect_response": null,
"search_tags": null,
"apple_pay_recurring_details": null,
"revenue_recovery": {
"total_retry_count": 2,
"payment_connector_transmission": "ConnectorCallSucceeded",
"billing_connector_id": "mca_ppEVMjRWgTiGyCiwFtg9",
"active_attempt_payment_connector_id": "mca_MbQwWzi4tFItmgYAmshC",
"billing_connector_payment_details": {
"payment_processor_token": "pm_1S4olu2KXBHSNjodBbiedqY9",
"connector_customer_id": "cus_T0qSE723C5Xxic"
},
"payment_method_type": "card",
"payment_method_subtype": "credit",
"connector": "stripe",
"billing_connector_payment_method_details": {
"type": "card",
"value": {
"card_network": "Visa",
"card_issuer": "Wells Fargo NA"
}
},
"invoice_next_billing_time": null,
"invoice_billing_started_at_time": null,
"first_payment_attempt_pg_error_code": "resource_missing",
"first_payment_attempt_network_decline_code": null,
"first_payment_attempt_network_advice_code": null
}
}
}
},
"timestamp": "2025-09-07T20:05:02.792Z"
}
Checklist
- [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
Changed Files
| File | Status |
|---|---|