Webhooks
Don't call us, we'll call you
Webhooks are used to notify of status changes to a configured endpoint. The webhook details can be configured in the Developer area of Portal at the aggregator or payer level. If a non 20x response is returned, we will retry the webhook 2 additional times in 15 seconds, then repeat the cycle again within the next 2 hours.
Webhooks are sent for the following events:
- Payment status updates
- Transaction status updates
- Account status updates
- Prepaid Account created via API
- Card Account successfully added via Widget UI
- Bank Account successfully added via Widget UI
- Bank Account status updated upon Chargeback/Reveral request
- Dispute status updates for Instant Funding (AFT) transactions
- Dispute opened
- Dispute challenged and under review
- Dispute updated
- Dispute closed
- Dispute response deadline reminder
Aggregator API Model & WebhooksIf the API Account is created at the Aggregator level, webhooks will be invoked based on the Aggregator webhook settings.
Webhook Idempotency
Webhook payloads will include a header x-webhook-id with a unique value that corresponds with the Aws-Api-Gateway-Requestid header response value from the originating Create request.
Dispute webhooks are not started by one of your API requests, so their x-webhook-id header is empty. To detect a repeated dispute webhook, use public_dispute_id, webhook_type and last_modified_date together. For DISPUTE_REMINDER, also use reminder_type, since two different reminders for the same dispute can carry the same last_modified_date. The same dispute event can be delivered more than once, so treat the status in the most recent delivery as current.
Webhook Payload schemas
PAYMENT WEBHOOK PAYLOAD
{
"webhook_type": "PAYMENT",
"payment_id": "String : Payment ID",
"payment_status": "Enum : Payment Status",
"reference_id": "String (optional) : Reference ID if included"
}TRANSACTION WEBHOOK PAYLOAD
{
"webhook_type": "TRANSACTION",
"type": "Enum : Transaction Type",
"method": "Enum: Transaction Method",
"transaction_id": "String : Transaction ID",
"transaction_status": "Enum : Transaction Status",
"reference_id": "String (optional) : Reference ID if included",
"webhook_created_date": "Timestamp",
"network_approval_code": "String : Approval code for Instant Deposit"
}{
"webhook_type": "TRANSACTION",
"transaction_id": "String : Transaction ID",
"transaction_status": "Enum : Transaction Status",
"reference_id": "String (optional) : Reference ID if included",
"error_code": "String: error code",
"error_message": "String: error detail"
}
Apple Pay Transaction WebhooksThis section is subject to change. Apple Pay support is under development. Details here can change until the integration is finalized.
Apple Pay transactions use the same
TRANSACTIONwebhooks as other transactions, in the payloads shown above. Apple Pay has no separate webhooks. If you already receive transaction webhooks, you don't need to make changes for Apple Pay.
PAYMENT ACCOUNT WEBHOOK PAYLOAD - Bank Account
{
"widget_id": "String: Widget ID",
"webhook_type": "PAYMENT_ACCOUNT",
"account_id": "String : Account ID",
"recipient_id": "String : Recipient ID",
"account_number": "String : Last 4 of Account Number",
"account_type": "String: CHECKING or SAVINGS",
"bank_name": "String"
}{
"widget_id": "String: Widget ID",
"webhook_type": "PAYMENT_ACCOUNT",
"account_id": "String : Account ID",
"recipient_id": "String : Recipient ID",
"account_status": "String : Status of Bank Account"
}{
"widget_id": "String: Widget ID",
"webhook_type": "PAYMENT_ACCOUNT",
"account_id": "String : Account ID",
"recipient_id": "String : Recipient ID",
"error_code": "String: error code",
"error_message": "String: error detail"
}
Bank Account WebhookThe
account_statusvalue may contain the following:
BANK_ACCOUNT_BLOCKED: Account is blocked due to fraud controls
PLAID_REAUTH_REQUIRED: Account authorization with Plaid is due to expire or will expire. Once the authorization expires, a Bank Account Widget Request to reauthorize the account with Plaid will be required before transacting. The reauthorization process may take place at any point in time.
PLAID_PERMISSION_REVOKED: User has explicitly revoked permission for Plaid to interact with their bank account. A new Bank Account Widget Request to authorize the account is required before transacting.
PAYMENT ACCOUNT WEBHOOK PAYLOAD - Card Account
{
"widget_id": "String: Widget ID",
"webhook_type": "PAYMENT_ACCOUNT",
"account_id": "String : Account ID",
"recipient_id": "String : Recipient ID",
"card_number": "String Masked card number"
}{
"widget_id": "String: Widget ID",
"webhook_type": "PAYMENT_ACCOUNT",
"account_id": "String : Account ID",
"recipient_id": "String : Recipient ID",
"card_number": "String : Masked card number",
"error_code": "String : Error code for account sharing (ERR_ACCOUNT_SHARING_LIMIT_EXCEEDED)"
}{
"widget_id": "String: Widget ID",
"webhook_type": "PAYMENT_ACCOUNT",
"account_id": "String : Account ID",
"recipient_id": "String : Recipient ID",
"card_number": "String Masked card number",
"verification_result" : {
"network": "String: VISA/MASTERCARD",
"pan": "String: Match result",
"cvv": "String: Match result",
"avs": {
"street": "String: Match result",
"zip": "String Match result"
},
"ani" {
"first_name": "String: Match result",
"last_name": "String: Match result"
}
}
}PAYMENT ACCOUNT WEBHOOK PAYLOAD - Prepaid Account
{
"widget_id": "String: Widget ID",
"webhook_type": "PAYMENT_ACCOUNT",
"account_id": "String : Account ID",
"recipient_id": "String : Recipient ID",
"prepaid_account_status": "String : Account Status",
"prepaid_account_error": "String : Account Error Detail"
}DISPUTE WEBHOOK PAYLOAD
{
"webhook_type": "Enum : Dispute Webhook Type",
"reminder_type": "Enum (optional) : Dispute Reminder Type, DISPUTE_REMINDER only",
"webhook_created_date": "Timestamp",
"transaction_id": "String : Transaction ID",
"public_dispute_id": "String : Public Dispute ID",
"external_transaction_id": "String (optional) : External ID of the disputed transaction",
"case_number": "String : Card network case number",
"amount_disputed": "Decimal : Amount disputed",
"currency_iso_code": "String : USD",
"status": "Enum : Dispute Status",
"current_workflow_stage": "Enum : Dispute Workflow Stage",
"kind": "Enum : AFT",
"reason": "String : Dispute reason",
"reason_code": "String : Card network reason code",
"dispute_provider": "String : Dispute Provider",
"created_at": "Timestamp : When the dispute was created",
"reply_by_date": "Timestamp : Response deadline for the current stage",
"response_due_in_days": "Integer : Whole days until reply_by_date",
"last_modified_date": "Timestamp : When the dispute last changed",
"responsible_party": "Enum : Responsible Party",
"responsible_party_description": "String : Description of the responsible party",
"corrective_action_required": "Boolean (optional) : Mastercard only, true when rejected evidence must be resubmitted",
"corrective_action_due_date": "Timestamp (optional) : Mastercard only, deadline to resubmit evidence",
"transaction_amount": "Decimal : Original transaction amount",
"transaction_status": "Enum : Transaction Status",
"transaction_type": "Enum : Transaction Type",
"transaction_date": "Timestamp : When the transaction was created",
"transaction_paid_date": "Timestamp : When the transaction was paid",
"network_approval_code": "String (optional) : Approval code for card-based transactions",
"network_transaction_id": "String (optional) : Network ID for card-based transactions",
"url": "String (optional) : Link to retrieve the full dispute"
}Dispute webhooks are sent only for disputes on Instant Funding (AFT) transactions. Every dispute event uses this payload. For the
webhook_type,reminder_type,status,current_workflow_stage,responsible_partyanddispute_providervalues, see About Disputes. The payload does not include recipient details, evidence or status history; useurlto retrieve the full dispute.
Webhook Verification
Webhook payloads include an x-verification header that contains a JSON Web Token (JWT) to verify the webhook contents and origin. It is recommend that the webhook signature and contents are verified before processing the payload.
Using the JWT to Verify Webhooks
1. Decode the x-verification header JWT
x-verification header JWTUse your library of choice to decode the x-verification header JWT.
x-verification : eyJ0eXAiOiJKV1QiLCJraWQiOiI5OGQ1ZTA4ZS1mNTNlLTQ4MjEtYmZkZC0zZjYwZmIwYTRkMDgiLCJhbGciOiJSUzI1NiJ9.eyJpYXQiOjE2NTc1NjkwNTAsInJlcXVlc3RfYm9keV9zaGEyNTZfaGFzaCI6IkI4QjBENDYxOEQ5RjdEMTkzREYwQUE4MTMyRjYzNTY2QkI3MUQ1N0NEM0RCODdGQjQ0RDBDMTEwRTAxMDM2NjYifQ.Kzb-awXSL0bZ0vldAP_VHQ8N3P_TMwaz8b2WICX9Kb1tgYhoIPxpEtTm6zRv3LEAZTHtegsiM5AR8vxK5JSJgzInOicUpG-JSbuHzgX6ofWHFrPJtGnHPOwxJn54s7Z2MYTe1AnSsgA_86IxD8Tqgka_UZAyr__KJigqONRaVxiEdGuMjrn61J-sbKefvCwqJUtP2HuMYZDf3dMSzo0NI6zWBeskPueaJXSuAjLRAWVwbnmOTYbGFxe2GhwQTkcLCGqtDwE2YPm3xp9zc30ite8Xy5JKxiSZr17QNE6oTFDKW9ykcg8QMZJoNu6WO4kdFDsyTcBRTJMmEaWifrud9VaVT1-GqoOsKlPIBE0l3dmaAajxNyeOXkbinpcIvxLkn291_sKRgPAbzajRT9DuTuR5YzuqV9uQYKKCWhkpPDcYs16kbOzmZCKstUCDldV5yIx1vCF3iq8A_S56RIgdr4D4_LNPyFSeWkf-e7ZOCcdXyOvB8n-X2o-70Gte0Tx-gPT6sr0MW2CESEVOxPe-MSxs54_Ns1yQc5LLCtuYMpzssjTy3JaLJvNFVP2Gw2EqsGY7cJEqSy2YGXCe1kDpCA889dnzXnwGTralCxyWgfDotqu48zZ6v1cBjlWNZGh_I9YwJvIUpRyp9VBSi2QzCfWNBeHoo5xjF0lUVC1h5dUDecoding the example JWT x-verification header results in the following JWT header and payload:
{
"typ": "JWT",
"kid": "98d5e08e-f53e-4821-bfdd-3f60fb0a4d08",
"alg": "RS256"
}{
"iat": 1657569050,
"request_body_sha256_hash": "B8B0D4618D9F7D193DF0AA8132F63566BB71D57CD3DB87FB44D0C110E0103666"
}2. Get the Validation Key
Make a GET request to TESTSANDBOXURL/api/v2/{payerId}/webhooks/get_validation_key/{kid}, replacing {payerId} with your payer id, and {kid} with the kid obtained from the JWT header. The response contains a public key to verify the authenticity of the JWT payload.
{
"kty": "RSA",
"e": "AQAB",
"n": "ylNCSJtagLrhi2D0aITP8mdFl6sAo4LPePyUgqejemKdGQPtH1YDKuetMSTv5nDbMjdGPxA6VqJPAFFnMdbz3q4pYBPFTo18pMw7bbKN9P7tS_eQ6KZABclz2Fy7oyrJBv3NdVVNpEW8CkrJr8R3CUIpkJtv_z-hV7PMLxmBDDaDnU0bf4q_mFCAbk2uXc32CNxVntQL1bULKg5YYE6pJrpMpZ6pf7UuWF1Gw7OFVS1OBa9gcu_hzHR015Mu12b4HRNCDzULR3PBdM9XvjmXIk97AjWpPPGiHNUwflfEHGMf2bMIbM63tTYdi3yZIqTn8VeZRjym3Dw6gSj11WK_FEiNmHRzTmhw43iOU7Mq8rd0mSccBOEr1XyD2TkQ1ttFz5qgDTUTfzlucshCTkkiF8p8V_waKtb_5U5sxtZ65a4Xs_9HkIfIrEIXZzLYOeEInEDD1h_UWrOu8DrZN7l6SliYbnD0nBqLxszN9k17ZsA-PbrUyuTmrNj3m8puVTZWjpFbWQr_aKhQMBsbjkDksVb2oae0QKgwlB5qwkf4wb-hGzviGx0fpjHFcwomU1pU1trGgBQFXt33vTi-IRsrgBczOw0d0vLHhmqlnWlaHpSFIcYA63HBuijcgwK3Ufqc71-ytwd4HRpEhWxhPZXK0KgIRt2yVDoKgeyEkpE7FIM"
}3. Verify the Authenticity of the JWT
Use the key obtained in step 2 to verify the signature of the JWT. Your JWT library should provide a convenient method to handle the verification. If the signature is not valid, do not process the webhook.
4. Read the JWT Payload and Confirm the Webhook Hash.
Finally, parse the contents of the JWT payload, example again below. The two keys,iat and request_body_sha256_hash are used to verify the time the webhook was sent and the hash of the webhook body. Compare the request_body_sha256_hash (the hash itself is case insensitive) with the SHA-256 hash of the webhook body to confirm they match. If these hashes don't match, do not process the webhook.
The iat value is the timestamp in seconds indicating when the webhook was created. For security reasons, the webhook should be rejected if the webhook is more than 5 minutes old. If verification is successful, process the webhook payload.
{
"iat": 1657569050,
"request_body_sha256_hash": "B8B0D4618D9F7D193DF0AA8132F63566BB71D57CD3DB87FB44D0C110E0103666"
}Updated 7 days ago

