Receiving Async Webhook Notifications & Signature Verification
Why Use Webhook
The processing of payment and funds-related operations (such as payouts) typically involves a time delay, making it a classic asynchronous process. After you call the BasicEx Payment API to create a payment invoice or a payout request, and when the final funds status changes, we need a mechanism to actively notify your system. This is what a Webhook does.
By passing a notificationUrl parameter in your API request (or configuring a default callback address in the merchant portal), BasicEx Payment will send a POST request containing a JSON data payload to your service when specific events occur. Once your system receives these pushes and completes the signature verification, you can execute the corresponding local order fulfillment or status transition operations.
Rule for Returning HTTP 200
After your server correctly receives and processes the Webhook notification, it MUST return an HTTP 200 OK response with an empty body to indicate that you have successfully consumed the notification. If BasicEx Payment receives any non-200 response code (including timeouts), it will consider the delivery a failure and initiate a multi-level exponential backoff retry mechanism to push the event to you repeatedly until the retry limit is exhausted.
Async Event Object (Event Object)
Each time we push data to your Webhook endpoint, the structure is as follows:
{
"id": "500e8384-61b1-4219-855d-2a196330af52",
"object": "event",
"objectId": "40620230325105039975309362381014",
"created": 1680064028243,
"type": "invoice.completed",
"data": {
"orderNo": "40620230325105039975309362381014",
... // Specific business object data
},
"retriesNum": 0
}Event Object Structure Description
| Field Name | Type | Description |
|---|---|---|
| id | string | Event ID, unique identifier for each Webhook event |
| objectId | string | The primary key of the business entity generating the event (e.g., invoice number) |
| object | string | Fixed value: event |
| created | long | 13-digit millisecond timestamp of when the event was generated |
| type | string | Type of the notification event (routing flag), refer to the table below |
| data | object | The specific business payload corresponding to the event, varying by type |
| retriesNum | int | Indicates the retry attempt number for this Webhook event |
Comprehensive Core Event Types (Event Type)
When parsing the Event Object, you should first evaluate the type field to determine which processing logic to invoke:
Notification Event Type (type) | Business Scenario Description |
|---|---|
invoice.paid | Event notification indicating the payment invoice has been successfully paid. This triggers after success but does not signify the invoice is finalized. Rely only on invoice.completed for final confirmation. 👉 Invoice Data Structure |
invoice.partial_completed | The payment invoice has experienced a partial payment during its validity period, but the amount paid is less than the total invoice amount. Merchants must use the paidAmount field to ascertain the current paid amount. 👉 Invoice Data Structure |
invoice.completed | Event notification that the payment invoice has been completed. This signifies that the invoice has reached its finalized state. 👉 Invoice Data Structure |
invoice.expired | Event notification that the payment invoice has expired. This triggers after expiration, indicating the transaction is closed. 👉 Invoice Data Structure |
payout.completed | Successful payout event. Triggers after a payout is successful, representing a completed payout. 👉 Payout Data Structure |
payout.failed | Failed payout event. Triggers after a payout fails due to reasons like incorrect addresses or insufficient balance, allowing the merchant to unlock frozen funds. 👉 Payout Data Structure |
Webhook Security Signature Verification
To prevent malicious users from simulating BasicEx Payment and sending spoofed "payment successful" callbacks to your server, you MUST perform a secure signature verification on every POST request that hits your notificationUrl!
Critical Pitfall: Must Use the Raw, Unparsed HTTP Body Text Stream
Regardless of the verification method adopted, the "string to be signed" definition is: The Webhook notification callback URL you passed + the raw HTTP Body (JSON text stream) sent to you by BasicEx.Please remember: Never use a JSON string that has been intercepted by a framework, deserialized into an Object, and then converted back. Doing so will alter property ordering, spaces, or line breaks, which will inevitably result in a hash mismatch!
Step 1 of Verification: Determine the Verification Mechanism
The push initiated by BasicEx will carry a declaration named X-Webhook-Signature-Type in the HTTP Header. You should use this to determine which verification logic branch to follow:
- Value is
key: Indicates you are configured to use the API Key mode. - Value is
cert: Indicates you are configured to use the platform API certificate mode.
Branch A: Processing key (API Key Mode HMAC Verification)
If using API Key authentication, when we push to you, we will use the Secret Key configured in your backend to generate an HMAC-SHA512 signature for the payload.
Extract the Signature Header Retrieve the critical signature from the HTTP Header:
X-Webhook-Signature.Calculate Independently and Match
- Retrieve your
Secret Keyobtained from the merchant portal as the Key. - Calculate the
HMAC-SHA512hash against the string to be signed (URL + raw JSON stream) mentioned earlier. - Convert this binary result into an all-lowercase Hexadecimal string (Hex).
- Compare the calculated Hex string against the
X-Webhook-Signatureyou received in step 1. If they match exactly, the request is secure and unaltered.
- Retrieve your
Branch B: Processing cert (Platform Certificate Mode RSA Verification)
In this mode, the push message is signed by the BasicEx platform using the platform's private key. Therefore, merchants need to obtain the platform certificate to perform the paired verification.
Extract Relevant Headers
- Extract the signature head:
X-Webhook-Signature(This string is the Base64 encoded signature data). - Extract the platform certificate serial number:
X-Webhook-Signature-Serial.
- Extract the signature head:
Parse and Locate the Corresponding Platform Certificate
- Based on the certificate serial number obtained in the previous step, call the
Get Platform Certificate APIbelow (or read from a local cache) to find the platform certificate data string corresponding to that serial number. Note: You must use the platform certificate for verification here; you cannot use your own merchant certificate.
- Remove the PEM header/footer markers from the platform certificate and parse out a standard RSA
PublicKeydevelopment object.
- Based on the certificate serial number obtained in the previous step, call the
Decrypt and Verify the Signature Authenticity
- Call the
Verifyfunction in your development language environment. - Specify the encryption method as
SHA-256 with RSA(i.e., SHA256withRSA / RSA-SHA256). - Take the value of the
X-Webhook-Signatureextracted in step 1 and perform a Base64 Decode back into the original binary stream array. - Invoke the verification function, passing in both the string to be signed (URL + raw JSON stream) and the Base64 Decoded binary stream, and use the platform public key to verify hash consistency. If the function returns
True, authentication passes.
- Call the
TIP
If you find this process too complex or error-prone, we strongly recommend referring to how our BasicEx Java SDK encapsulates Webhook verification and porting it to your own system.
Get Platform Certificate API Details
You only need to pull this when you are receiving callbacks via the cert mode:
Endpoint:
https://openapi.basicex.com/v2/platform/certificateMethod:
GET
Response Parameters (response.data):
| Field | Type | Description |
|---|---|---|
| certificate | string | Platform certificate data |
| serialNumber | string | Platform certificate serial number |