Skip to main content

Webhook

To receive real-time notifications about payments with SP Cuvex, you must configure a webhook in your system.

Webhook Integration

For your system to receive notifications about transactions in real time, complete the following process:

  1. Provide a publicly accessible endpoint that receives events in JSON format via POST requests.
  2. Generate a secret string, share it with us, and store it securely. This secret will be used to sign each incoming request.
  3. For each event received at the webhook, verify its authenticity using the shared secret and the signature included in the request headers.
  4. After verifying the signature, respond immediately.

Endpoint

Create a public endpoint where we will send event notifications in JSON format via POST requests.

In the x-sign header we will send a signature generated with the HMAC-SHA256 algorithm, using the previously shared secret and the request body (payload). This signature ensures that the request was sent by SP Cuvex.


Secret String

During your enrollment process, you must share both the endpoint URL and a secret string with us.

This string will be used to generate the HMAC-SHA256 signature for each request. It must be alphanumeric, random, high entropy, and no longer than 128 characters.

Store it securely on your server. Never include it in source code or repositories (Git, etc.).


Signature Verification

Each request will also include an x-sign header, similar to the following:

sha256=757107ea0eb2509fc211221cce984b8a37570b6d7586c22c46f4379c8b043e17

This signature is generated with the algorithm:

HMAC-SHA256(UTF-8(secret), body)

You must replicate the signature calculation using your securely stored secret string and the received payload.

  • Use UTF-8 for text-to-bytes conversion.
  • Compare the received signature with the locally generated one using a timing-safe comparison.
  • Remember to omit the sha256= prefix before comparing.
  • If the signatures do not match, respond with HTTP 401.

Examples

@RestController
public class WebhookController {

@PostMapping(value = "/webhook", consumes = MediaType.APPLICATION_JSON_VALUE)
public ResponseEntity<Void> webhookEndpoint(HttpServletRequest request) throws IOException,
NoSuchAlgorithmException,
InvalidKeyException {

String signature = request.getHeader("x-sign");
byte[] body = this.getPayloadBytes(request);

this.validateSignature(signature, body);

// Do your data processing asynchronously to respond ASAP

return ResponseEntity.ok().build();
}

public byte[] getPayloadBytes(HttpServletRequest request) throws IOException {

ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
byte[] buffer = new byte[1024];
int bytesRead;

InputStream inputStream = request.getInputStream();

while ((bytesRead = inputStream.read(buffer)) != -1) {
outputStream.write(buffer, 0, bytesRead);
}

return outputStream.toByteArray();
}

public void validateSignature(final String receivedSignatue, final byte[] body) throws NoSuchAlgorithmException, InvalidKeyException {
HexFormat hexFormatter = HexFormat.of();
Mac hmacSha256 = Mac.getInstance("HmacSHA256");
String keyString = "Sup3rS4cretT3xt!";
String cleanedReceivedSign = receivedSignatue.replace("sha256=", "");
byte[] receivedHashBytes = hexFormatter.parseHex(cleanedReceivedSign);

SecretKeySpec secretKey = new SecretKeySpec(keyString.getBytes(StandardCharsets.UTF_8), "HmacSHA256");
hmacSha256.init(secretKey);
byte[] calculatedHash = hmacSha256.doFinal(body);

if (!Arrays.equals(calculatedHash, receivedHashBytes)) {
throw new RuntimeException("Signatures don't match");
}
}

}

Handling Incoming Data and Response

After verifying the signature, respond immediately.
Process heavy workloads asynchronously in your system.

Expected responses (without payload):

  • 200: signature verified.
  • 401: invalid signature.
  • 500: unexpected error.

Replay Protection

We include x-timestamp (UTC, seconds) and x-id (unique nonce per event).
Reject requests with x-timestamp outside a ±5 minute window and repeated x-id values.

Retries

If your endpoint responds with anything other than 2xx, we will retry using exponential backoff (e.g., up to 5 attempts in ~5 minutes).
Deduplicate by x-id.


Event Definitions

Payment Created

This event will be sent whenever a new payment is created.

{
"event": "PAYMENT_CREATED",
"data": {
"id": "fca84a27-2a4c-413c-9f0d-edff3c25959e",
"reference": "INV-09-2025-0001",
"network": "TRON",
"token": "USDT",
"amount": "5.25",
"confirmed_amount": "0",
"receiver": "TRjE1H8dxypKM1NZRdysbs9wo7huR4bdNz",
"status": "OPEN",
"valid_until": 1755532150,
"created_at": "2024-04-16T17:44:51Z",
"updated_at": "2024-04-16T17:44:51Z"
}
}

Payment Finished

Event sent when a payment is confirmed on the blockchain.

{
"event": "PAYMENT_FINISHED",
"data": {
"id": "fca84a27-2a4c-413c-9f0d-edff3c25959e",
"reference": "INV-09-2025-0001",
"network": "TRON",
"token": "USDT",
"amount": "5.25",
"confirmed_amount": "5.25",
"receiver": "TRjE1H8dxypKM1NZRdysbs9wo7huR4bdNz",
"status": "FINISHED",
"valid_until": 1755532150,
"created_at": "2024-04-16T17:44:51Z",
"updated_at": "2024-04-16T17:46:12Z",
"txs": [
{
"tx": "6e3a8368bcc7113cef086156fc4500b3322fa258781652cdc56a855c12e5cb3b",
"block_number": "75279138",
"sender_address": "TVAKUM28o1pNj6pkcckvWHFUs1foRcSwAt",
"amount": "123.456789"
}
]
}
}

Payment Late Finished

Event sent when a payment is confirmed but outside the expected time window.

{
"event": "PAYMENT_LATE_FINISHED",
"data": {
"id": "fca84a27-2a4c-413c-9f0d-edff3c25959e",
"reference": "INV-09-2025-0001",
"network": "TRON",
"token": "USDT",
"amount": "5.25",
"confirmed_amount": "5.25",
"receiver": "TRjE1H8dxypKM1NZRdysbs9wo7huR4bdNz",
"status": "LATE_PAYMENT",
"valid_until": 1755532150,
"created_at": "2024-04-16T17:44:51Z",
"updated_at": "2024-04-16T17:46:12Z",
"txs": [
{
"tx": "6e3a8368bcc7113cef086156fc4500b3322fa258781652cdc56a855c12e5cb3b",
"block_number": "75279138",
"sender_address": "TVAKUM28o1pNj6pkcckvWHFUs1foRcSwAt",
"amount": "123.456789"
}
]
}
}

Payment Expired

Event sent when the validity period expires without payment confirmation.

{
"event": "PAYMENT_EXPIRED",
"data": {
"id": "fca84a27-2a4c-413c-9f0d-edff3c25959e",
"reference": "INV-09-2025-0001",
"network": "TRON",
"token": "USDT",
"amount": "5.25",
"confirmed_amount": "0",
"receiver": "TRjE1H8dxypKM1NZRdysbs9wo7huR4bdNz",
"status": "EXPIRED",
"valid_until": 1755532150,
"created_at": "2024-04-16T17:44:51Z",
"updated_at": "2024-04-16T17:44:51Z"
}
}

Payment Failed

Event sent if an error occurs during payment processing.

{
"event": "PAYMENT_FAILED",
"data": {
"id": "fca84a27-2a4c-413c-9f0d-edff3c25959e",
"reference": "INV-09-2025-0001",
"network": "TRON",
"token": "USDT",
"error_message": "The network is offline",
"amount": "5.25",
"confirmed_amount": "0",
"receiver": "TRjE1H8dxypKM1NZRdysbs9wo7huR4bdNz",
"status": "FAILED",
"valid_until": 1755532150,
"created_at": "2024-04-16T17:44:51Z",
"updated_at": "2024-04-16T17:44:51Z"
}
}