Webhook — Payment Events
Overview
The wallet-payment-service emits webhook events to notify merchant systems of payment state changes. Events are delivered as HTTP POST requests to the merchant's registered webhook URL. All events use at-least-once delivery — merchants must handle duplicate events idempotently.
Event Catalog
| Event Name | Trigger | Payment Methods | Payment Types |
|---|---|---|---|
PAYMENT_SUCCEEDED | All allocations completed successfully | CARD, BANK_ACCOUNT | SALE, PRE-AUTH (after capture) |
PAYMENT_AUTHORIZED | All allocations authorized (pre-auth) | CARD | PRE-AUTH |
PAYMENT_ACCEPTED | ACH accepted by processor | BANK_ACCOUNT | SALE |
PAYMENT_CANCELLED | All allocations cancelled | CARD, BANK_ACCOUNT | SALE, PRE-AUTH |
PAYMENT_FAILED | Payment processing failed | CARD, BANK_ACCOUNT | SALE, PRE-AUTH |
Common Payload Envelope
Every webhook event follows this envelope structure:
{
"name": "<EVENT_NAME>",
"source": "<x-source header value or null>",
"payload": {
"id": "<paymentId>",
"merchantTransactionId": "<merchantTransactionId>",
"checkoutId": "<checkoutId or null>",
"amount": 15000,
"currencyCode": "USD",
"status": "<payment status>",
"authorizeCard": false,
"partialAuthorization": false,
"customer": { ... },
"merchant": { ... },
"paymentAllocations": [ ... ],
"paymentDateUtc": "2026-04-04T10:30:00Z",
"metadata": { ... }
}
}
PAYMENT_SUCCEEDED
Trigger: All payment allocations processed successfully (card charged, ACH settled).
Sample Payload — Single Card
{
"name": "PAYMENT_SUCCEEDED",
"source": "merchant-portal",
"payload": {
"id": "550e8400-e29b-41d4-a716-446655440000",
"merchantTransactionId": "order-20260404-001",
"amount": 15000,
"currencyCode": "USD",
"status": "COMPLETED",
"authorizeCard": false,
"partialAuthorization": false,
"customer": {
"firstName": "John",
"lastName": "Doe",
"email": "john.doe@example.com"
},
"paymentAllocations": [
{
"id": "a1b2c3d4-e5f6-7890-1234-567890abcdef",
"amount": 15000,
"authorizedAmount": 15000,
"capturedAmount": 15000,
"status": "COMPLETED",
"paymentMethod": {
"type": "CARD",
"card": {
"lastFour": "4242",
"brand": "visa",
"expMonth": 12,
"expYear": 2028
}
},
"vendor": {
"name": "STRIPE",
"transactionId": "pi_3NabcXYZ123456789"
}
}
],
"paymentDateUtc": "2026-04-04T10:30:00Z"
}
}
Sample Payload — Split-Tender (2 Cards)
{
"name": "PAYMENT_SUCCEEDED",
"source": "merchant-portal",
"payload": {
"id": "660e8400-e29b-41d4-a716-446655440000",
"merchantTransactionId": "split-20260404-001",
"amount": 20000,
"status": "COMPLETED",
"paymentAllocations": [
{
"id": "aaaa1111-2222-3333-4444-555566667777",
"amount": 12000,
"status": "COMPLETED",
"paymentMethod": { "type": "CARD", "card": { "lastFour": "4242", "brand": "visa" } }
},
{
"id": "bbbb1111-2222-3333-4444-555566667777",
"amount": 8000,
"status": "COMPLETED",
"paymentMethod": { "type": "CARD", "card": { "lastFour": "1234", "brand": "mastercard" } }
}
]
}
}
PAYMENT_AUTHORIZED
Trigger: Pre-auth hold placed on card. Awaiting explicit capture.
Sample Payload
{
"name": "PAYMENT_AUTHORIZED",
"source": "merchant-portal",
"payload": {
"id": "770e8400-e29b-41d4-a716-446655440000",
"merchantTransactionId": "preauth-20260404-001",
"amount": 25000,
"status": "AUTHORIZED",
"authorizeCard": true,
"partialAuthorization": true,
"paymentAllocations": [
{
"id": "cccc1111-2222-3333-4444-555566667777",
"amount": 25000,
"authorizedAmount": 25000,
"capturedAmount": 0,
"status": "AUTHORIZED"
}
]
}
}
PAYMENT_ACCEPTED
Trigger: ACH bank account payment accepted by processor (not yet settled).
Sample Payload
{
"name": "PAYMENT_ACCEPTED",
"source": "merchant-portal",
"payload": {
"id": "880e8400-e29b-41d4-a716-446655440000",
"merchantTransactionId": "ach-20260404-001",
"amount": 50000,
"status": "ACCEPTED",
"paymentAllocations": [
{
"id": "dddd1111-2222-3333-4444-555566667777",
"amount": 50000,
"status": "ACCEPTED",
"paymentMethod": {
"type": "BANK_ACCOUNT",
"bankAccount": { "lastFour": "6789", "bankName": "Chase" }
}
}
]
}
}
PAYMENT_CANCELLED
Trigger: Payment successfully cancelled (void completed).
Sample Payload
{
"name": "PAYMENT_CANCELLED",
"source": "merchant-portal",
"payload": {
"id": "770e8400-e29b-41d4-a716-446655440000",
"merchantTransactionId": "preauth-20260404-001",
"amount": 25000,
"status": "CANCELLED",
"paymentAllocations": [
{
"id": "cccc1111-2222-3333-4444-555566667777",
"amount": 25000,
"status": "CANCELED"
}
]
}
}
PAYMENT_FAILED
Trigger: Payment processing failed (card declined, vendor error, etc.).
Sample Payload — Single Failure
{
"name": "PAYMENT_FAILED",
"source": "merchant-portal",
"payload": {
"id": "990e8400-e29b-41d4-a716-446655440000",
"merchantTransactionId": "failed-20260404-001",
"amount": 10000,
"status": "FAILED",
"paymentAllocations": [
{
"id": "eeee1111-2222-3333-4444-555566667777",
"amount": 10000,
"status": "FAILED",
"error": {
"code": "card_declined",
"message": "Your card was declined."
}
}
]
}
}
Sample Payload — Split-Tender with Rollback
{
"name": "PAYMENT_FAILED",
"source": "merchant-portal",
"payload": {
"id": "660e8400-e29b-41d4-a716-446655440000",
"amount": 20000,
"status": "FAILED",
"paymentAllocations": [
{
"id": "aaaa1111-2222-3333-4444-555566667777",
"amount": 12000,
"status": "ROLLED_BACK"
},
{
"id": "bbbb1111-2222-3333-4444-555566667777",
"amount": 8000,
"status": "FAILED",
"error": {
"code": "card_declined",
"message": "Your card was declined."
}
}
]
}
}
Delivery Guarantees
| Property | Behavior |
|---|---|
| Delivery model | At-least-once (merchants must handle duplicates) |
| Retry policy | Exponential backoff on delivery failure |
| Ordering | Not guaranteed — events may arrive out of order |
| Idempotency | Use paymentId + name (event) to detect duplicates |
| Security | HMAC signature verification, OAuth2/JWT authentication |
| Timeout | Merchant endpoint must respond within 30 seconds |
| Expected response | 2xx status code to acknowledge receipt |
Related Documentation
- Refund Events (coming soon)
- Webhook Reference (OpenAPI)
- Error Handling & Retry Strategies