Authentication¶
2. Base URL¶
| Environment | Base URL |
|---|---|
| Production | https://api.360cuzdan.com |
A separate test (UAT) URL, if available for your integration, is shared at onboarding. Examples below use the production base URL.
3. Credentials¶
| Value | Description |
|---|---|
| API Key | Sent in the X-API-Key header. Identifies your account. |
| Secret Key | Used to sign requests and to verify the callback hashcode. Never expose it client-side. |
The secret key is shared once at creation. If lost, it must be regenerated (the old one stops working).
4. Authentication (HMAC-SHA256)¶
Every request to /paymentapi/v1/* requires these headers:
| Header | Description | Constraint |
|---|---|---|
X-API-Key |
Your API key | Must be active |
X-Signature |
HMAC-SHA256 signature | Hex (lowercase) |
X-Timestamp |
Unix timestamp (seconds) | Within ±5 minutes of server time |
X-Nonce |
Per-request unique value | 8–64 chars, never reused |
X-Callback-URL |
Per-transaction callback override | Optional — must point to the same scheme + host (+ port) as the webhook URL registered on your account |
Content-Type |
application/json |
— |
X-Callback-URLrestriction: the header is only accepted when it targets the same scheme + host (+ port) as your registered webhook URL — you can change the path, not the destination. A header pointing anywhere else (or sent while no webhook is registered on your account) is rejected with 400X-Callback-URL must match the firm's registered webhook host. If no callback URL can be resolved at all, the request fails with 400Callback URL not configured on provider.Platform integrations: if your payment platform (e.g. a turnkey/aggregator provider) has a central callback URL registered with Cuzdan360, all results are delivered there — this header and your account webhook are not used. See section 6.
Signature¶
Message to sign (lines joined with literal \n newlines):
HTTP_METHOD:POSTorGET(uppercase)PATH: e.g./paymentapi/v1/deposits(path without query string)BODY: the raw JSON body (sign the exact bytes you send); forGETrequests with no body, an empty string — the message then contains an emptyBODYlineTIMESTAMP,NONCE: same values as the headers
The path is part of the signature. Sign the full versioned path, including the
/paymentapi/v1prefix. Signing a bare/deposits, or a path from the previous unversioned contract, produces a different digest and the request is rejected with 401Invalid signature.
Example (bash)¶
API_KEY="<api-key>"
SECRET_KEY="<secret-key>"
TIMESTAMP=$(date +%s)
NONCE=$(openssl rand -hex 16)
BODY='{"amount":"1000","bank_code":"1","first_name": "Test","last_name": "User","username": "testuser","user_id":"user001","reference":"TX-DEP-001","method":"fast"}'
MESSAGE="POST\n/paymentapi/v1/deposits\n${BODY}\n${TIMESTAMP}\n${NONCE}"
SIGNATURE=$(printf "%b" "${MESSAGE}" | openssl dgst -sha256 -hmac "${SECRET_KEY}" | awk '{print $2}')
curl -X POST "https://api.360cuzdan.com/paymentapi/v1/deposits" \
-H "Content-Type: application/json" \
-H "X-API-Key: ${API_KEY}" \
-H "X-Signature: ${SIGNATURE}" \
-H "X-Timestamp: ${TIMESTAMP}" \
-H "X-Nonce: ${NONCE}" \
-H "X-Callback-URL: https://yoursite.com/cuzdan360/callback" \
-d "${BODY}"
Common mistake: the
BODYyou sign and the body you send must be byte-identical. Do not re-serialize the JSON — sign and send the same string.
Complete client implementations (PHP, Node.js, Python) are in section 10.