Authentication
The CheckYout Partner API uses two authentication mechanisms: an API key for your requests to CheckYout, and a webhook secret to verify the events CheckYout sends to you.
API key
The API key authenticates your requests to the CheckYout API (e.g. POST /api/v1/notify). You create one in the CheckYout dashboard under Settings → API keys.
Format
| Property | Value |
|---|---|
| Prefix | cyo_ |
| Length | Prefix + 32 hexadecimal characters |
| Example | cyo_a1b2c3d4e5f6... |
| Storage | SHA-256 hashed (clear text shown only once) |
Shown only once
The API key is displayed in clear text exactly once on creation. After that, CheckYout only stores its SHA-256 hash. If you lose the key, create a new one.
Usage
Send the API key in the X-API-Key header on every request:
curl -X POST https://checkyout.app/api/v1/notify \
-H "Content-Type: application/json" \
-H "X-API-Key: cyo_YourApiKey" \
-d '{"device_id": "...", "phone": "+41..."}'Webhook signature
If you configured a webhook secret on the API key, CheckYout signs every webhook with HMAC-SHA256. The signature is sent in theX-CheckYout-Signature header as a hexadecimal string.
| Header | Value |
|---|---|
X-CheckYout-Signature | HMAC-SHA256 hex digest of the JSON body using your webhook secret |
Verify the signature
Compute the HMAC-SHA256 of the raw request body using your webhook secret and compare it to the value in the header:
const crypto = require('crypto');
function verifySignature(body, secret, signature) {
const expected = crypto
.createHmac('sha256', secret)
.update(JSON.stringify(body))
.digest('hex');
return crypto.timingSafeEqual(
Buffer.from(expected, 'hex'),
Buffer.from(signature, 'hex')
);
}
// Inside your webhook handler:
app.post('/webhooks/checkyout', (req, res) => {
const signature = req.headers['x-checkyout-signature'];
const secret = process.env.CHECKYOUT_WEBHOOK_SECRET;
if (secret && signature) {
if (!verifySignature(req.body, secret, signature)) {
return res.status(401).json({ error: 'Invalid signature' });
}
}
// Process the event...
res.status(200).json({ received: true });
});import hmac
import hashlib
import json
def verify_signature(body: dict, secret: str, signature: str) -> bool:
expected = hmac.new(
secret.encode("utf-8"),
json.dumps(body, separators=(",", ":")).encode("utf-8"),
hashlib.sha256,
).hexdigest()
return hmac.compare_digest(expected, signature)
# Inside your webhook handler (Flask):
@app.route("/webhooks/checkyout", methods=["POST"])
def handle_webhook():
signature = request.headers.get("X-CheckYout-Signature")
secret = os.environ.get("CHECKYOUT_WEBHOOK_SECRET")
if secret and signature:
if not verify_signature(request.json, secret, signature):
return jsonify({"error": "Invalid signature"}), 401
# Process the event...
return jsonify({"received": True}), 200<?php
function verifySignature(string $body, string $secret, string $signature): bool {
$expected = hash_hmac('sha256', $body, $secret);
return hash_equals($expected, $signature);
}
// Inside your webhook handler:
$body = file_get_contents('php://input');
$signature = $_SERVER['HTTP_X_CHECKYOUT_SIGNATURE'] ?? '';
$secret = getenv('CHECKYOUT_WEBHOOK_SECRET');
if ($secret && $signature) {
if (!verifySignature($body, $secret, $signature)) {
http_response_code(401);
echo json_encode(['error' => 'Invalid signature']);
exit;
}
}
$event = json_decode($body, true);
// Process the event...
http_response_code(200);
echo json_encode(['received' => true]);Note on the Python signature
CheckYout serialises the body with JSON.stringify() (JavaScript). Make sure your serialisation produces the same format — no extra whitespace. When in doubt, verify against the raw request body rather than re-serialising.
Best practices
- Store the API key in an environment variable, never in source code.
- Rotate the key immediately if you suspect compromise.
- Use
timingSafeEqual(or its equivalent) when comparing signatures to prevent timing attacks. - Verify the webhook signature before processing the event.
- Log failed signature checks for your security monitoring.
Next: Webhooks in detail