Skip to Content

Webhook

Webhook allows you to receive real-time asynchronous notifications from SUNBAY when transaction status changes. Through Webhook, you can update order status promptly and trigger business processes without frequent polling of query interfaces.

Overview

When a transaction status changes to any of the following states, SUNBAY will send a Webhook notification to your configured notifyUrl:

  • SUCCESS - Transaction successful
  • FAIL - Transaction failed
  • CLOSED - Transaction closed

Supported transaction types include:

  • SALE (Sale)
  • AUTH (Pre-authorization)
  • POST_AUTH (Pre-authorization completion)
  • FORCED_AUTH (Forced authorization)
  • INCREMENTAL (Incremental authorization)
  • REFUND (Refund)
  • VOID (Void)

And all other transaction scenarios.

Request Specification

Basic Information

PropertyDescription
Request MethodPOST
Request URLThe notifyUrl you provided when initiating the transaction
Content-Typeapplication/json; charset=utf-8
Connection Timeout5 seconds
Read Timeout10 seconds

Response Requirements

Your service needs to return an HTTP 200 status code to indicate successful receipt. Any non-200 status code (including 4xx, 5xx) will be considered a failure and trigger the retry mechanism.

Security Verification

Each Webhook request includes the following headers for verifying request origin and preventing replay attacks:

HeaderDescription
X-Client-Request-IdUnique request ID (UUID format), used for log tracing and idempotent processing
X-TimestampRequest timestamp (milliseconds), recommended to verify timeliness (e.g., valid within 5 minutes) to prevent replay attacks
X-SignatureRequest signature (HMAC-SHA256), used to verify request integrity

Signature Verification

The signature is generated using the HMAC-SHA256 algorithm:

  • Signature Key: Webhook signing secret configured in SUNBAY Copilot application details
  • Signature Content: Raw request body JSON string (maintain field order and format consistency)
  • Signature Format: Hexadecimal lowercase string

Notification Content

The Webhook request body is in JSON format and contains complete transaction information.

All amount fields are integers in the smallest currency unit (e.g., cents for USD, yen for JPY).

Field Description

To ensure backward compatibility, we may add new fields in future versions. Please ensure your parsing logic can ignore unknown fields to avoid parsing failures due to new fields.

FieldTypeDescription
transactionIdstringSUNBAY transaction ID
transactionRequestIdstringTransaction request ID, used to correlate requests
referenceOrderIdstringYour business order number
transactionStatusstringTransaction status: S success, F failed, C closed
transactionTypestringTransaction type, such as SALE, AUTH, REFUND, etc.
createTimestringTransaction creation time. Format: yyyy-MM-DDTHH:mm:ss+TIMEZONE (ISO 8601)
Format: date-time
Example: “2023-11-19T10:30:00+08:00”
completeTimestringTransaction completion time. Format: yyyy-MM-DDTHH:mm:ss+TIMEZONE (ISO 8601)
Format: date-time
Example: “2023-11-19T10:32:00+08:00”
priceCurrencystringCurrency codes (ISO 4217), such as USD, CNY
orderAmountintegerOrder amount (smallest currency unit)
transactionAmountintegerActual transaction amount (smallest currency unit)
taxAmountintegerTax amount (smallest currency unit)
surchargeAmountintegerSurcharge amount (smallest currency unit)
tipAmountintegerTip amount (smallest currency unit)
cashbackAmountintegerCashback amount (smallest currency unit)
maskedPanstringMasked card number, e.g., 411111******1111
cardNetworkTypestringCard network type, such as VISA, MASTERCARD
paymentMethodstringPayment method, such as CARD, WALLET
subPaymentMethodstringSub payment method
batchNointegerBatch number
voucherNostringVoucher number
stanstringSystem trace audit number
rrnstringRetrieval reference number
authCodestringAuthorization code
entryModestringEntry mode, such as SWIPE, INSERT, TAP
authenticationMethodstringAuthentication method, such as PIN, SIGN
transactionResultCodestringTransaction result code
transactionResultMsgstringTransaction result description
terminalSnstringTerminal serial number
descriptionstringPayment description
attachstringAdditional data (pass-through field)

Example

A typical Webhook request example:

{ "transactionId": "T202512160001", "transactionRequestId": "REQ202512160001", "referenceOrderId": "ORDER_10001", "transactionStatus": "S", "transactionType": "SALE", "createTime": "2023-11-19T10:30:00+08:00", "completeTime": "2023-11-19T10:30:10+08:00", "priceCurrency": "USD", "orderAmount": 1000, "transactionAmount": 950, "taxAmount": 50, "surchargeAmount": 0, "tipAmount": 0, "cashbackAmount": 0, "maskedPan": "411111******1111", "cardNetworkType": "VISA", "paymentMethod": "CARD", "subPaymentMethod": "CREDIT", "batchNo": 1, "voucherNo": "000123", "stan": "000456", "rrn": "123456789012", "authCode": "A12345", "entryMode": "CHIP", "authenticationMethod": "PIN", "transactionResultCode": "00", "transactionResultMsg": "Approved", "terminalSn": "SN1234567890", "description": "Payment for order ORDER_10001", "attach": "custom_data_example" }

Verify Signature

To ensure the request is from SUNBAY, you need to verify the X-Signature header.

Verification Steps

  1. Get the raw request body (JSON string)
  2. Use your Webhook signing secret to perform HMAC-SHA256 signature on the request body
  3. Convert the calculation result to a hexadecimal lowercase string
  4. Compare with the X-Signature header

Code Examples

Below are complete examples of receiving and verifying Webhook signatures:

import org.springframework.web.bind.annotation.*; import org.springframework.http.ResponseEntity; import org.springframework.http.HttpStatus; import javax.servlet.http.HttpServletRequest; import javax.crypto.Mac; import javax.crypto.spec.SecretKeySpec; import java.io.BufferedReader; import java.nio.charset.StandardCharsets; import java.util.Map; @RestController @RequestMapping("/webhook") public class WebhookController { // Signing secret obtained from SUNBAY Copilot application details private static final String WEBHOOK_SECRET = "your_webhook_secret_key"; @PostMapping("/notify") public ResponseEntity<Map<String, String>> handleWebhook(HttpServletRequest request) { try { // 1. Get request headers String signature = request.getHeader("X-Signature"); String requestId = request.getHeader("X-Client-Request-Id"); String timestamp = request.getHeader("X-Timestamp"); // 2. Read raw request body (do not deserialize) String payload = getRequestBody(request); // 3. Verify signature if (!verifySignature(payload, signature, WEBHOOK_SECRET)) { return ResponseEntity.status(HttpStatus.UNAUTHORIZED) .body(Map.of("code", "INVALID_SIGNATURE", "message", "Signature verification failed")); } // 4. Verify timeliness (optional, prevent replay attacks) if (!verifyTimestamp(timestamp)) { return ResponseEntity.status(HttpStatus.UNAUTHORIZED) .body(Map.of("code", "EXPIRED", "message", "Request expired")); } // 5. Parse request body and process business logic WebhookNotifyRequest notifyRequest = JSON.parseObject(payload, WebhookNotifyRequest.class); processWebhook(notifyRequest, requestId); // 6. Return success response return ResponseEntity.ok(Map.of("code", "SUCCESS", "message", "Received")); } catch (Exception e) { log.error("Failed to process webhook", e); return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR) .body(Map.of("code", "ERROR", "message", "Internal error")); } } /** * Read raw request body */ private String getRequestBody(HttpServletRequest request) throws Exception { StringBuilder sb = new StringBuilder(); try (BufferedReader reader = request.getReader()) { String line; while ((line = reader.readLine()) != null) { sb.append(line); } } return sb.toString(); } /** * Verify signature */ private boolean verifySignature(String payload, String signature, String secret) { try { Mac mac = Mac.getInstance("HmacSHA256"); SecretKeySpec secretKeySpec = new SecretKeySpec( secret.getBytes(StandardCharsets.UTF_8), "HmacSHA256" ); mac.init(secretKeySpec); byte[] hash = mac.doFinal(payload.getBytes(StandardCharsets.UTF_8)); String expectedSignature = bytesToHex(hash); return expectedSignature.equalsIgnoreCase(signature); } catch (Exception e) { log.error("Signature verification error", e); return false; } } /** * Convert byte array to hexadecimal string */ private String bytesToHex(byte[] bytes) { StringBuilder result = new StringBuilder(); for (byte b : bytes) { result.append(String.format("%02x", b)); } return result.toString(); } /** * Verify timeliness (optional) */ private boolean verifyTimestamp(String timestamp) { try { long requestTime = Long.parseLong(timestamp); long currentTime = System.currentTimeMillis(); // Only accept requests within 5 minutes return Math.abs(currentTime - requestTime) <= 5 * 60 * 1000; } catch (Exception e) { return false; } } /** * Process business logic (idempotent processing) */ private void processWebhook(WebhookNotifyRequest request, String requestId) { // Check if already processed (idempotent) if (isAlreadyProcessed(request.getTransactionId())) { log.info("Transaction already processed: {}", request.getTransactionId()); return; } // Execute business logic updateOrderStatus(request); // Mark as processed markAsProcessed(request.getTransactionId()); } }

Important Notes

  • Get the raw request body string first, do not deserialize or reorder fields, use it directly for signature verification
  • Always verify the signature before processing business logic
  • Signature verification failure should return HTTP 401 status code
  • Business processing exceptions should return HTTP 500 status code

Processing Notifications

Idempotency

Due to network reasons, you may receive multiple Webhook notifications for the same transaction. You need to ensure your processing logic is idempotent to avoid business issues caused by duplicate processing (such as duplicate shipments or duplicate inventory deductions).

Recommended idempotency keys:

  • transactionId
  • transactionRequestId
  • X-Client-Request-Id

Processing Flow

It is recommended to process Webhook notifications according to the following flow:

  1. Verify Signature - Confirm the request is from SUNBAY
  2. Check Idempotency Key - Query locally whether the transaction has been processed
  3. Execute Business Logic - Update order status, trigger subsequent processes
  4. Record Processing Status - Mark as processed to prevent duplication
  5. Return 200 - Inform SUNBAY of successful receipt

Best Practices

  1. Quick Response: It is recommended to adopt an asynchronous processing mode, return HTTP 200 immediately after receiving the notification, then put the task in a queue for asynchronous processing
  2. Unified Status Management: Transaction results may be obtained through multiple channels (synchronous return, active query, Webhook). It is recommended to use transactionId as the primary key for unified status management
  3. Log Recording: Completely record key fields such as X-Client-Request-Id and transactionId for easy troubleshooting

Retry and Circuit Breaking

Retry Strategy

When your service returns a non-HTTP 200 status code or the request times out, we will automatically retry.

Retry CountRetry Interval
1-35 seconds
4-630 seconds
71 minute
85 minutes
930 minutes
102 hours
114 hours
126 hours

Maximum of 12 retries, after which it will be marked as final failure.

Circuit Breaking Mechanism

To protect both systems, we implement a circuit breaking strategy. When a notification address reaches the failure threshold consecutively, notifications to that address will be suspended.

The following are default configurations (may be adjusted based on specific circumstances):

Failure CountCircuit Breaking Duration
≥ 20 times1 hour
≥ 50 times6 hours
≥ 100 times24 hours

Important Notes

  • Ensure Webhook service is highly available and quickly returns HTTP 200 status code
  • Do not rely entirely on Webhook, it is recommended to use transaction query interface for compensation to avoid missing orders

Response Format

We only care about the HTTP 200 status code, the response body content is not mandatory. However, it is recommended to return standard JSON format for easy log analysis and troubleshooting.

Success Response

HTTP/1.1 200 OK Content-Type: application/json { "code": "SUCCESS", "message": "Received" }

Failure Response

HTTP/1.1 500 Internal Server Error Content-Type: application/json { "code": "INTERNAL_ERROR", "message": "Service temporarily unavailable" }

Testing and Go-Live

Testing Checklist

Before going live, please ensure:

  • Signature verification logic is correct
  • Idempotent processing logic is complete
  • Asynchronous processing mechanism is ready
  • Log recording is complete (including key fields such as X-Client-Request-Id, transactionId)
  • Exception handling (timeout, retry, etc.)
  • Monitoring and alerting configuration is complete

Production Environment Recommendations

  • High Availability: Ensure Webhook service is stable and available to avoid triggering circuit breaking
  • Asynchronous Processing: Interface quickly returns HTTP 200, business logic is put in queue for asynchronous processing
  • Monitoring and Alerting: Monitor key metrics such as receipt success rate and processing time
  • Fallback Plan: Use transaction query interface to regularly compensate for missed notifications
Last updated on