Embeddable button
The call-me button is a small button you add to your own admin panel, CRM or order screen. Someone on your team presses it, picks the purpose and topic, and the assistant calls the customer. The button is a single dependency-free script and needs the Call-me button (and Outbound calls) features in your plan.
Security model
Section titled “Security model”- No secret key goes to the browser. The button uses a publishable key (
rz_pub_…): it only has thecalls:requestscope and only accepts requests from your allowed domains. - A publishable key is public and the
Originheader can be forged outside a browser, so the key alone cannot start a call. Every request needs a one-time token valid for at most 15 minutes, issued by your server with the secret key. - The token binds the triggering person (your own user who opened the page) and the call’s target — exactly one of:
sepet_id,siparis_noormusteri_telefon.- If the target is a cart or order, the customer details (phone, email, name) are read from the record in Zitena; whatever the browser sends is ignored.
- If the target is a phone number, only its hash goes into the token; the phone sent by the browser must match that hash.
- Adding a cart or order other than the target returns
403 target_mismatch.
- The per-key rate limit only counts requests from an allowed origin (nobody else can use up your key and lock the button); failed attempts are limited to 20 per minute per IP.
- The request takes the same path as
POST /api/v1/aramalar: İYS, calling hours, quotas and the do-not-call list apply as usual. Its source is recorded asbuton.
-
Create two keys under Settings → API keys:
- A secret key with the
calls:writescope for your server (rz_live_…). - A publishable key for the button (
rz_pub_…); enter the domains the button will run on (e.g.https://panel.example.com).
- A secret key with the
-
When building the page, issue a token on your server (a new token on every page load):
Terminal window curl -s -X POST "$RZ_API/api/v1/buton-token" \-H "Authorization: Bearer rz_live_xxx" -H "Content-Type: application/json" \"hedef": {"siparis_no": "560991"}, "gecerlilik_sn": 600}'# {"token": "eyJ2Ijox…", "son_gecerlilik": "2026-09-27T10:25:00Z"} -
Add the script to the page and print the token:
<script src="https://<your-console-host>/api/v1/zitena-buton.js"data-anahtar="rz_pub_xxx"data-token="eyJ2Ijox…"data-etiket="Sizi arayalım"defer></script>
Server-side examples
Section titled “Server-side examples”<?php// On the order screen: issue a token to have this order’s customer called.function rz_button_token(string $orderNo, array $user): ?string { $res = wp_remote_post(RZ_API . '/api/v1/buton-token', [ 'timeout' => 5, 'headers' => ['Authorization' => 'Bearer ' . RZ_KEY, 'Content-Type' => 'application/json'], 'body' => wp_json_encode([ 'tetikleyen' => ['ad' => $user['name'], 'eposta' => $user['email']], 'hedef' => ['siparis_no' => $orderNo], 'gecerlilik_sn' => 600, ]), ]); if (is_wp_error($res) || wp_remote_retrieve_response_code($res) !== 200) { return null; } return json_decode(wp_remote_retrieve_body($res), true)['token'] ?? null;}?><?php if ($token): ?><script src="<?= esc_url(RZ_API . '/api/v1/zitena-buton.js') ?>" data-anahtar="<?= esc_attr(RZ_PUB_KEY) ?>" data-token="<?= esc_attr($token) ?>" defer></script><?php endif; ?>// Express example: a token targeting the cart on the cart page.app.get('/panel/sepet/:id', async (req, res) => { const r = await fetch(`${process.env.RZ_API}/api/v1/buton-token`, { method: 'POST', headers: { Authorization: `Bearer ${process.env.RZ_KEY}`, 'Content-Type': 'application/json' }, body: JSON.stringify({ tetikleyen: { ad: req.user.name, eposta: req.user.email }, hedef: { sepet_id: req.params.id }, }), }); const { token } = await r.json(); res.render('sepet', { rzToken: token, rzPubKey: process.env.RZ_PUB_KEY, rzApi: process.env.RZ_API });});import os, requests
def button_token(tetikleyen: dict, hedef: dict, ttl: int = 600) -> str: r = requests.post(f"{os.environ['RZ_API']}/api/v1/buton-token", headers={"Authorization": f"Bearer {os.environ['RZ_KEY']}"}, json={"tetikleyen": tetikleyen, "hedef": hedef, "gecerlilik_sn": ttl}, timeout=5) r.raise_for_status() return r.json()["token"]
{"musteri_telefon": "05321234567"})# With a phone-bound token, also add data-musteri-ad / data-musteri-telefon to the page.Script attributes
Section titled “Script attributes”| Attribute | Required | Description |
|---|---|---|
data-anahtar | yes | Publishable key (rz_pub_…) |
data-token | yes | The token issued by your server |
data-etiket | no | Button text (default “Sizi arayalım” — “We’ll call you”) |
data-musteri-ad, data-musteri-telefon, data-musteri-eposta | only with a phone-bound token | Customer details; the phone must match the hash in the token |
data-siparis-no, data-sepet-id | no | Must equal the token’s target; otherwise target_mismatch |
data-asistan | no | Assistant name in the dialog title (up to 40 characters); defaults to “Asistan arasın” |
data-marka-renk | no | #RRGGBB; only applied to the dialog’s 3 px top strip. Buttons always use the product accent |
What the user sees
Section titled “What the user sees”Pressing the button opens a modal dialog: purpose (your company’s active purposes), a required topic (what the assistant needs to know, 10–1000 characters, with a counter) and optionally a specific time within calling hours. After sending, the result is shown in the dialog: queued, scheduled (time in Istanbul time and why) or won’t be called (e.g. “the customer asked not to be called”). The button’s interface is in Turkish.
A token is good for one request: a second request gets 401 button_token_used; for a new request the page is reloaded with a new token. An expired token returns 401 invalid_button_token and the dialog asks the user to reload the page.
Browser endpoints
Section titled “Browser endpoints”The script calls these endpoints itself (they are not in the OpenAPI schema):
| Endpoint | Description |
|---|---|
GET /api/v1/zitena-buton.js | The script (cached for 1 hour) |
GET /api/v1/buton/amaclar | Active purposes and calling hours; needs a valid token (doesn’t consume it) |
POST /api/v1/buton/aramalar | Sends the request; headers Authorization: Bearer rz_pub_… and X-Zitena-Buton-Token |
CORS is only open on these endpoints and only for allowed origins; the preflight (OPTIONS) only reflects an origin that is on some publishable key’s allow list.
Error codes: invalid_button_token, button_token_used, target_mismatch, target_not_found, cart_without_phone, order_without_phone, musteri_required, invalid_phone, origin_not_allowed, publishable_key_required, too_many_failures, validation_error (field list hatalar: [{alan, etiket, mesaj}]).
Accessibility
Section titled “Accessibility”The interface lives in a Shadow DOM (your site’s CSS doesn’t interfere) and text is written with textContent (no HTML injection). The dialog is a modal <dialog>: Esc closes it, focus stays inside and returns to the button on close. Status messages are announced with aria-live; colours meet AA contrast in light and dark themes.
© 2026 Erusoft