KEZAYO Meet
PricingSolutionsEnterpriseResourcesDownload
JoinSign inStart Free
JoinSign inStart Free
Developers

Partner API — promo codes

Generate KEZAYO promo codes for your customers through a signed HTTPS API. Your customers redeem them at checkout on KEZAYO; the discount is applied by our server.

On this page

  1. 1. Overview
  2. 2. Credentials
  3. 3. Signing requests
  4. 4. Examples
  5. 5. Endpoints
  6. 6. Errors
  7. 7. Limits
  8. 8. Webhook
  9. 9. Customer experience

1. Overview

  • Base URL: https://kezayo.com/api/partner/v1
  • JSON in, JSON out. Money is integer paise (₹1 = 100).
  • Every request is HMAC-SHA256 signed — no cookies, bearer tokens or sessions. HTTPS only.
  • Discount tiers: 10, 20, 40, 50, 70, 80 or 100% — your account is limited to the tiers we agree with you.

2. Credentials

We create your partner account and issue an API key: a key id (kzk_…, sent with every request) and a secret (kzs_…, never sent). The secret is shown to us exactly once and handed to you over a secure channel; KEZAYO does not store it, so it cannot be recovered — if it is lost we issue a new key.

Up to two keys can be active at once, so you can rotate without downtime: start signing with the new key, then ask us to revoke the old one.

3. Signing requests

Send four headers: X-KZ-Key (key id), X-KZ-Timestamp (Unix seconds), X-KZ-Nonce (16–64 random characters [A-Za-z0-9_-], new for every request) and X-KZ-Signature:

canonical = "KZ1-HMAC-SHA256" + "\n"
          + METHOD                 (upper-case, e.g. POST)
          + "\n" + PATH_AND_QUERY  (exactly as sent, e.g. /api/partner/v1/promo-codes?limit=50)
          + "\n" + TIMESTAMP
          + "\n" + NONCE
          + "\n" + hex(SHA-256(raw request body bytes))   (empty body → SHA-256 of "")

X-KZ-Signature = lower-case hex HMAC-SHA256(secret, canonical)
  • The timestamp must be within ±300 seconds of our clock — keep NTP on.
  • A (key, nonce) pair is accepted once; re-sending a signed request is refused with REPLAY. To retry, sign again with a new nonce and reuse your Idempotency-Key.
  • Sign the exact bytes you send, with Content-Type: application/json when there is a body (max 64 KB).
  • For a GET the body hash is e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855.

4. Examples

Node.js (18+)

import crypto from 'node:crypto';

const BASE = 'https://kezayo.com';
const KEY_ID = process.env.KZ_KEY_ID;   // kzk_…
const SECRET = process.env.KZ_SECRET;   // kzs_…  (keep it in a secret store)

export async function kezayo(method, pathAndQuery, body, idempotencyKey) {
  const raw = body === undefined ? '' : JSON.stringify(body);
  const ts = String(Math.floor(Date.now() / 1000));
  const nonce = crypto.randomBytes(16).toString('hex');
  const bodyHash = crypto.createHash('sha256').update(raw).digest('hex');
  const canonical = ['KZ1-HMAC-SHA256', method.toUpperCase(), pathAndQuery, ts, nonce, bodyHash].join('\n');
  const signature = crypto.createHmac('sha256', SECRET).update(canonical).digest('hex');
  const res = await fetch(BASE + pathAndQuery, {
    method,
    headers: {
      'X-KZ-Key': KEY_ID, 'X-KZ-Timestamp': ts, 'X-KZ-Nonce': nonce, 'X-KZ-Signature': signature,
      ...(raw ? { 'Content-Type': 'application/json' } : {}),
      ...(idempotencyKey ? { 'Idempotency-Key': idempotencyKey } : {}),
    },
    body: raw || undefined,
  });
  const json = await res.json();
  if (!res.ok) throw Object.assign(new Error(json.error), { status: res.status, code: json.code, requestId: json.requestId });
  return json;
}

// 25 single-use 20%-off codes for Pro 100, valid until 31 Dec 2026:
const batch = await kezayo('POST', '/api/partner/v1/promo-codes', {
  percent: 20, count: 25, plans: ['PRO_100'], validUntil: '2026-12-31T23:59:59+05:30', campaign: 'Diwali 2026',
}, 'order-48213');
console.log(batch.codes.map((c) => c.code));

curl + openssl

KEY_ID='kzk_…'; SECRET='kzs_…'
BODY='{"percent":20,"count":5,"campaign":"Diwali 2026"}'
REQ_PATH='/api/partner/v1/promo-codes'
TS=$(date +%s); NONCE=$(openssl rand -hex 16)
BODY_HASH=$(printf '%s' "$BODY" | openssl dgst -sha256 -hex | sed 's/^.* //')
SIG=$(printf 'KZ1-HMAC-SHA256\nPOST\n%s\n%s\n%s\n%s' "$REQ_PATH" "$TS" "$NONCE" "$BODY_HASH" \
  | openssl dgst -sha256 -hmac "$SECRET" -hex | sed 's/^.* //')
curl -sS "https://kezayo.com$REQ_PATH" \
  -H "X-KZ-Key: $KEY_ID" -H "X-KZ-Timestamp: $TS" -H "X-KZ-Nonce: $NONCE" -H "X-KZ-Signature: $SIG" \
  -H 'Content-Type: application/json' -H 'Idempotency-Key: order-48213' --data "$BODY"

5. Endpoints

POST /promo-codes — create codes

FieldTypeDefaultNotes
percentnumber—Required. One of your allowed tiers (subset of 10, 20, 40, 50, 70, 80, 100), ≤ your maximum.
countint11–500 random codes, PREFIX-XXXX-XXXX (no 0/O/1/I/L).
codestring—Instead of count: one custom code starting with your prefix, 4–32 letters/digits (hyphens allowed). Sending both code and count is BAD_REQUEST; a single custom code has batchId null.
plansstring[]your plansPlan ids from GET /plans. Empty = every plan you may discount.
cyclestringanymonthly, annual or any.
maxRedemptionsint1Uses per code, 1–1000.
maxPerAccountint1Uses per KEZAYO account, ≤ maxRedemptions.
validFromISO-8601now
validUntilISO-8601+365 daysAt most 730 days ahead; exclusive.
firstPurchaseOnlyboolfalseOnly accounts that never subscribed.
campaignstring""Your label, ≤ 80 characters.

Optional header Idempotency-Key (8–100 characters from A-Z a-z 0-9 _ . : -): a retry with the same key within 7 days returns the same codes ("replayed": true, HTTP 200) and uses no extra quota. Use your order id. Response 201:

{ "batchId": "batch_…", "replayed": false, "count": 1,
  "codes": [ { "code": "ACME-7KQ2-M9XD", "batchId": "batch_…", "percent": 20, "plans": ["PRO_100"], "cycle": "any",
               "maxRedemptions": 1, "maxPerAccount": 1, "validFrom": null,
               "validUntil": "2026-12-31T18:29:59.000Z", "firstPurchaseOnly": false,
               "campaign": "Diwali 2026", "status": "active", "state": "active",
               "redeemed": 0, "remaining": 1, "createdAt": "…" } ] }

state is one of active, paused, revoked, scheduled, expired, exhausted.

GET /promo-codes — list your codes

Query: status (active | paused | revoked), campaign, batchId, limit (1–200, default 50), cursor. Returns { data, total, nextCursor }; pass nextCursor back as cursor (null = last page).

GET /promo-codes/{code} — one code with stats

Case-insensitive; hyphens optional. Returns the code, stats (redeemed, pending, remaining, listValueMinor, discountMinor, lastRedeemedAt) and redemptions (plan, cycle, prices, time). No customer personal data is ever returned.

POST /promo-codes/{code}/revoke — revoke a code

Optional body { "reason": "…" }. Permanent. Redemptions already made stand, and the quota the code used is not returned. Revoking an already revoked code returns it with "alreadyRevoked": true.

GET /usage — quota and limits

Monthly (resets on the 1st, UTC) and total quota used / remaining, your allowed tiers, plans, prefix and rate limit. Quota unit = redemption capacity: a request uses count × maxRedemptions units (25 single-use codes = 25; one 100-use code = 100).

GET /plans — plans and discounted prices

Every self-serve plan with its monthly and annual list price and, for each tier you may issue, the exact price your customer will pay. Rounding: discount = floor(list × percent / 100), final = list − discount.

6. Errors

Every error is { "error": "<message>", "code": "<CODE>", "requestId": "req_…" }. Every response carries an X-Request-Id header; quote it when you contact us. One exception: the 300 requests/minute per-IP ceiling is enforced in front of the API and answers HTTP 429 with only { "error": "…" } (no code or request id) — back off and retry.

HTTPcodeMeaning
400BAD_REQUEST, BAD_JSON, BAD_TIER, BAD_COUNT, BAD_CODE, BAD_PLAN, BAD_CYCLE, BAD_LIMIT, BAD_DATE, BAD_CAMPAIGN, BAD_FILTER, BAD_IDEMPOTENCY_KEYInvalid input — the message says what.
401MISSING_AUTH, BAD_TIMESTAMP, BAD_NONCE, BAD_SIGNATURENot signed, signed wrongly, or unknown key.
401CLOCK_SKEWTimestamp more than 300 s from server time.
401REPLAYThat nonce was already used with this key.
401KEY_REVOKEDThe key was revoked.
403PARTNER_SUSPENDEDYour account is suspended; your unredeemed codes do not work.
403IP_NOT_ALLOWED, HTTPS_REQUIREDRequest IP not on your allowlist / plain HTTP.
403TIER_NOT_ALLOWED, PLAN_NOT_ALLOWEDOutside what your account may issue.
403QUOTA_EXCEEDEDMonthly or total quota would be exceeded.
404NOT_FOUNDNo such code of yours.
409DUPLICATE_CODEA custom code with that text already exists.
413 / 415BODY_TOO_LARGE, UNSUPPORTED_MEDIA_TYPEBody over 64 KB / not application/json.
429RATE_LIMITEDOver your per-minute limit (default 60).
429TOO_MANY_FAILURES20 failed authentications from your IP in 10 minutes.
500INTERNALOur fault — retry with a new nonce and the same Idempotency-Key.

7. Limits

  • Your per-minute request limit (default 60), plus 300 requests/minute per source IP.
  • 500 codes per request, 1000 redemptions per code, validity at most 730 days.
  • 20 failed authentications from one IP in 10 minutes block that IP for the window.
  • Optional IP allowlist (single IPs or IPv4 CIDRs) set by us on request.

8. Webhook

If we configure a webhook URL (public https:// only) for you, each redemption of one of your codes is posted to it — best-effort, 5 s timeout, one retry after 30 s:

POST <your url>
Content-Type: application/json
X-KZ-Event: promo.redeemed
X-KZ-Timestamp: <unix seconds>
X-KZ-Signature: hex HMAC-SHA256(webhookSecret, timestamp + "\n" + rawBody)

{ "id": "evt_…", "event": "promo.redeemed", "at": 1790000000000,
  "data": { "code": "ACME-7KQ2-M9XD", "planId": "PRO_100", "cycle": "monthly",
            "percent": 20, "kind": "new", "redemptionId": "rdm_…" } }

The webhook secret (kzwh_…) is separate from your API secret and shown once. Verify the signature, reject timestamps older than 5 minutes, and de-duplicate on id. No customer personal data is sent.

9. Customer experience

  • One code per purchase, entered on the Plans page. Codes are case-insensitive; spaces and hyphens are ignored; codes cannot be combined.
  • The discount applies to the payment made now — a new subscription or an upgrade. Renewals are billed at the list price.
  • A 100% code activates the plan with a ₹0 invoice and no charge.
  • The invoice shows the list price, the code, the discount and the amount paid.
  • After 10 wrong codes an account (30 per IP) is locked out of promo codes for 15 minutes.
  • Cancelling keeps the plan to the end of the paid period; a used code is not returned. A refund returns only what was actually charged.
KEZAYO Meet

Meet. Present. Teach. Hire. Earn.
One platform for HD video meetings, webinars, whiteboards and a creator marketplace — by BIDUA Industries Pvt Ltd.

Support: Send a message · [email protected]

𝕏in▶⌨

Product

  • Features
  • Meetings
  • Webinars
  • Meeting Reports
  • Live Captions
  • Hire a Creator
  • Pricing
  • Download

Solutions

  • For Business
  • For Creators
  • For Educators
  • For Startups
  • Security & Privacy
  • Enterprise

Resources

  • Help Center
  • Blog
  • About
  • Contact
  • Partners
  • Refer & Earn
  • Partner API

Legal

  • Terms
  • Privacy
  • Acceptable Use
  • Community Guidelines
  • Refund Policy
  • Referral Programme Terms

© 2026 BIDUA Industries Pvt Ltd. All rights reserved.

🇮🇳 Proudly made in India

Developed and operated by BIDUA Industries Pvt Ltd. Payments and billing are provided by BIDUA Industries Pvt Ltd. Marketing partner: Ramaera Industries Ltd. Terms and Conditions apply.