API Documentation: Callback for Request
Overview
When a request is completed, client will be notified with the result via a callback. To facilitate this, the client must provide an endpoint that will be called by TSS Xellar.
Callback Requirements
Your callback endpoint should adhere to the following requirements:
- HTTP Method:
POST
- Response Status:
200 OK
- Max Timeout: 20 seconds
If the response is not 200 OK
or if the callback takes longer than 20 seconds, TSS Xellar will attempt to call your callback up to 3 times with an increasing backoff period.
Set up Callback Endpoint
To receive notifications from TSS Xellar, you'll need to set up an HTTP or HTTPS endpoint. However, It is strongly recommended that once your endpoint is publicly accessible, it must use HTTPS to ensure the security and integrity of the data transmitted. Using HTTPS helps protect the communication between TSS Xellar and your server by encrypting the data, preventing eavesdropping, tampering, and man-in-the-middle attacks.
Make sure your endpoint:
- Accepts POST requests.
- Handles incoming requests quickly, returning a successful status code (
2xx
) before processing any complex logic that may cause delays or timeouts. - Processes the request payload, which will contain a JSON object. Ensure your logic is non-blocking and quick to avoid timeouts.
Example:
const express = require('express');
const app = express();
// Parse incoming JSON requests
app.use(express.json());
app.post('/callback', (req, res) => {
// Respond quickly with status code 200
res.status(200).send();
// Additional logic can go here, but avoid blocking responses
});
app.listen(3000, () => {
console.log('Server is running on https://localhost:3000');
});
Verify callback signature
When integrating with our system via the callback endpoint, it's crucial to ensure that the incoming requests are authentic and untampered. To achieve this, we use HMAC signature verification to validate that the requests originate from a trusted source—our server.
Why Signature Verification is Necessary
Signature verification ensures that:
- The request data has not been modified during transit (integrity check).
- The request actually originates from our server and not from a third party (authenticity check).
- The request is recent, preventing replay attacks where old requests could be reused maliciously.
This mechanism uses a combination of:
- A shared secret between your system and ours (known only to both parties). The shared secret is the client secret that can be seen on Xellar Dashboard.
- A signature that is calculated based on specific request details (such as the HTTP method, URL, request body, and timestamp).
- A timestamp that ensures the request is not outdated, reducing the risk of replay attacks.
By verifying the signature, you can be confident that the callback request is legitimate, coming from our server, and hasn't been tampered with in transit.
- Extract the required details (signature, timestamp, HTTP method, URL, and request body) from the request.
- Recompute the signature using the same algorithm and shared secret that we use on our server.
- Compare the computed signature with the one provided in the
X-Signature
header of the request. If they match, the request is valid. - Ensure the timestamp in the
X-Timestamp
header is within an acceptable range to prevent replay attacks.
If any part of the verification fails, you should reject the request and handle it accordingly (e.g., return a 400 Bad Request
response).
By implementing this signature verification process, you ensure that the callback requests are genuine and come from our server, maintaining the security and integrity of your system.
const express = require('express');
const crypto = require('crypto');
const app = express();
const port = 8080;
// Secret key for HMAC verification (replace with your actual secret key)
const SECRET_KEY = 'your-client-secret-from-the-dashboard';
// Middleware to parse JSON body
app.use(express.json());
/**
* Minifies a JSON string by removing unnecessary spaces.
*/
function minifiedJSON(inputJSON) {
if (!inputJSON) {
return "";
}
try {
const parsed = JSON.parse(inputJSON);
return JSON.stringify(parsed); // Minified JSON
} catch (error) {
throw new Error(`Invalid JSON input: ${error.message}`);
}
}
/**
* Hashes the minified JSON using SHA256 and converts it to lowercase.
*/
function hashMinifiedJSON(inputJSON) {
const minified = minifiedJSON(inputJSON);
const hash = crypto.createHash('sha256').update(minified).digest('hex');
return hash.toLowerCase();
}
/**
* Generates the HMAC signature using the provided inputs.
*/
function generateSignature(method, url, clientSecret, hashedMinifiedJSON, timestamp) {
const stringToSign = `${method.toUpperCase()}:${url}:${hashedMinifiedJSON}:${timestamp}`;
const hmac = crypto.createHmac('sha256', clientSecret).update(stringToSign).digest();
return hmac.toString('base64');
}
/**
* Callback route to handle the incoming request and verify the signature
*/
app.post('/callback', (req, res) => {
const signature = req.headers['x-signature'];
const timestamp = req.headers['x-timestamp'];
const body = JSON.stringify(req.body); // Get the body of the request
// Hash the minified JSON
const hashedMinifiedJSON = hashMinifiedJSON(body);
if (!hashedMinifiedJSON) {
return res.status(500).send('Failed to hash minified JSON');
}
// Generate the self-calculated signature
const selfCalculatedSignature = generateSignature(req.method, req.originalUrl, SECRET_KEY, hashedMinifiedJSON, timestamp);
// Compare the received signature with the calculated signature
if (signature !== selfCalculatedSignature) {
return res.status(400).send('Invalid signature');
}
// If signature is valid, return 200 OK
res.status(200).send('OK');
});
// Start the server
app.listen(port, () => {
console.log(`Server running on http://localhost:${port}`);
});