# Callback integrity verification

Every callback request from KYCAID includes a cryptographic signature in the `x-data-integrity` HTTP header. You must verify this signature before processing the request to ensure authenticity and data integrity.

Reject requests with invalid signature
If the signature does not match, the request may have been tampered with or sent by an unauthorized party. Reject it immediately.

## Verification process

Where to find your API key
Go to [API keys](https://app.kycaid.com/api-keys) page of KYCAID Dashboard.

### Step 1. Read the raw request body

Read the incoming request body as a raw byte sequence (UTF-8). Do not parse it as JSON at this stage.

**Example**:


```json
{"request_id":"61a7dbcc012d9042e909cf006e7b412d6ba5","type":"VERIFICATION_STATUS_CHANGED","applicant_id":"4141cc1b18dba048470b2961cb4592f480fe","verification_id":"2cf795e713be1040e50b202164ee17bfdfbe","form_id":"58bed87600dd9944f02ba0c9cd8b32d6bd4c","verification_status":"pending"}
```

### Step 2. Encode the body using Base64

**Example**:


```
eyJyZXF1ZXN0X2lkIjoiNjFhN2RiY2MwMTJkOTA0MmU5MDljZjAwNmU3YjQxMmQ2YmE1IiwidHlwZSI6IlZFUklGSUNBVElPTl9TVEFUVVNfQ0hBTkdFRCIsImFwcGxpY2FudF9pZCI6IjQxNDFjYzFiMThkYmEwNDg0NzBiMjk2MWNiNDU5MmY0ODBmZSIsInZlcmlmaWNhdGlvbl9pZCI6IjJjZjc5NWU3MTNiZTEwNDBlNTBiMjAyMTY0ZWUxN2JmZGZiZSIsImZvcm1faWQiOiI1OGJlZDg3NjAwZGQ5OTQ0ZjAyYmEwYzljZDhiMzJkNmJkNGMiLCJ2ZXJpZmljYXRpb25fc3RhdHVzIjoicGVuZGluZyJ9
```

### Step 3. Compute the HMAC-SHA512 hash

Use your **API key** as the secret key and the Base64-encoded string from Step 2 as the input.

**Example**:

- Secret key (API token): `28c6f7cc0345a04eee0b535039b1c5a62547`
- Result (lowercase hex): `f7681b097b77928fc031d614709976796057c306cf77fdd449bb414937bd87678d908d7efaa65e9b1dd65b9eeea2121ea75bd9007f44fe8fcd7c9ac6cdeeef0e`


### Step 4. Compare with the `x-data-integrity` header

Compare the computed hash with the value from the `x-data-integrity` request header using a constant-time comparison function. If the two values are equal, the request is authentic. Otherwise, reject the request immediately.

## Code examples

### Node.js


```javascript
const crypto = require('crypto');

function verifyCallback(rawBody, xDataIntegrity, apiToken) {
  const base64Body = Buffer.from(rawBody, 'utf-8').toString('base64');
  const hash = crypto
    .createHmac('sha512', apiToken)
    .update(base64Body)
    .digest('hex');

  // Use timingSafeEqual to prevent timing attacks
  const hashBuffer = Buffer.from(hash, 'utf-8');
  const headerBuffer = Buffer.from(xDataIntegrity, 'utf-8');

  if (hashBuffer.length !== headerBuffer.length) {
    return false;
  }

  return crypto.timingSafeEqual(hashBuffer, headerBuffer);
}

// Usage in an Express handler
app.post('/webhook', express.raw({ type: 'application/json' }), (req, res) => {
  const isValid = verifyCallback(
    req.body,
    req.headers['x-data-integrity'],
    process.env.KYCAID_API_TOKEN
  );

  if (!isValid) {
    return res.status(401).send('Invalid signature');
  }

  const payload = JSON.parse(req.body);
  // Process the callback...
  res.status(200).send('OK');
});
```

### Python


```python
import base64
import hashlib
import hmac

def verify_callback(raw_body: bytes, x_data_integrity: str, api_token: str) -> bool:
    base64_body = base64.b64encode(raw_body).decode('utf-8')
    computed_hash = hmac.new(
        api_token.encode('utf-8'),
        base64_body.encode('utf-8'),
        hashlib.sha512
    ).hexdigest()

    # Use hmac.compare_digest to prevent timing attacks
    return hmac.compare_digest(computed_hash, x_data_integrity)
```

### PHP


```php
function verifyCallback(string $rawBody, string $xDataIntegrity, string $apiToken): bool
{
    $base64Body = base64_encode($rawBody);
    $hash = hash_hmac('sha512', $base64Body, $apiToken);

    // Use hash_equals to prevent timing attacks
    return hash_equals($hash, $xDataIntegrity);
}
```

## Notes

### Use constant-time comparison

Never compare signatures with `==` or `===`. Simple string comparison is vulnerable to timing attacks, where an attacker can infer the correct hash byte by byte based on response time differences. Always use the language-specific constant-time functions shown in the examples above.

### Read the raw body, not parsed JSON

Parsing and re-serializing JSON may alter whitespace or key order, producing a different Base64 value. Make sure your framework gives you access to the original request bytes before any parsing occurs.

### Respond promptly

Return an HTTP `200 OK` as soon as you have validated the signature and queued the event for processing. If KYCAID does not receive a timely response, the callback may be retried.

### Reject invalid signatures

If the computed hash does not match the `x-data-integrity` header, return HTTP `401` or `403` and do not process the payload.