Testing your integration
Before going live, test your integration. Zapnoty provides several tools: test keys, message preview and broadcast dry-run.
Two keys per project
Each project has two API keys: a live zn_live_ and a test zn_test_. It is the same project — shared subscribers, templates and webhook endpoints, the only difference is the key. When a project is created, the response contains both keys at once — the api_key and test_api_key fields.
What the test key is allowed to do
The test key zn_test_ is meant for verifying SENDING (runtime), not for managing the project. Hence its permissions are scoped:
Read — any GET request (templates, subscribers, tags, permissions, webhooks, logs, analytics).
Runtime operations (mocked in sandbox): /v1/send, /v1/send/batch, /v1/send/preview, /v1/otp/*, /v1/broadcast, /v1/auth/session, /v1/auth/verify, /v1/events/track, /v1/helpdesk/tickets/*, /v1/helpdesk/request, /v1/test/simulate-event.
Changing project settings — any mutating request (POST/PUT/PATCH/DELETE) to templates, permissions, tags, webhooks, sender, auto-messages, forms, helpdesk config (settings/SLA/ticket types/canned responses/routing rules), custom-bots, scheduler (scheduled/drip/recurring), key rotation, subscriber deletion → 403.
A settings-changing request with the test key returns 403 with the standard error envelope. This way the test key cannot accidentally be used to break the project: project setup is a one-time action done with the live key zn_live_ once during configuration.
Sandbox: what the sandbox mode is
A request with the test key zn_test_ goes through the full API logic (validation, rate-limit, delivery log, webhooks) and responds exactly like a live request — the same responses and error codes — but no real message is sent to Telegram/Max (mock delivery). Sandbox covers /v1/send, /v1/send/batch, /v1/otp/send and /v1/broadcast. Delivery log rows are marked is_test=true and are excluded from the project live statistics.
Simulating the delivery outcome
In sandbox mode you can add _test_directives to the /v1/send body to reproduce a specific outcome. delivery_outcome: "failed" routes the delivery down the failure path: a delivery log row with status failed and a delivery.failed webhook with the reason from failure_reason. Allowed failure_reason values: user_blocked_bot, chat_not_found, bot_banned, invalid_message, rate_limited, platform_unavailable, unknown. For a live key, _test_directives is silently ignored.
OTP in the sandbox
When /v1/otp/send is called with the test key, the response includes an extra sandbox_code field — the actual generated code in plaintext. Your CI/test can pass it straight to /v1/otp/verify without messenger access. For a live key the sandbox_code field is absent.
Message preview
POST /v1/send/preview renders the message (template, variables, signature) and returns the final text without sending. Useful to verify how vars are substituted and which format applies.
Broadcast dry-run
The dry_run: true parameter in /v1/broadcast does not create a broadcast — it returns the audience size under the filters, an estimated credit charge and a sample rendered message. Use it to verify filters (tags, permissions) before a real send.
Testing webhooks
To test event delivery, temporarily point a webhook endpoint at a test receiver URL (webhook.site, ngrok on localhost). The X-Zapnoty-Signature is computed the same way — verify your HMAC validation. The webhook delivery log is available in the dashboard. Deliveries from sandbox requests are marked is_test=true.
In sandbox the delivery.success / delivery.failed webhooks are dispatched with a ~2 second delay — this imitates real asynchrony (live webhooks arrive after the API response).
Manually triggering events
POST /v1/test/simulate-event is available only with the test key zn_test_. The endpoint actually delivers an arbitrary event to the project webhook endpoints with an HMAC signature and the is_test=true mark — handy to debug a handler for any event without reproducing the scenario. Body: event (a name from the known events list) and data (an arbitrary JSON object). An unknown event or non-object data → 400. If the project has no webhook endpoints, the response is dispatched: true, endpoints: 0.
Test key for an existing project
Projects created before sandbox have no test key. Generate one with the button in the dashboard: project settings → «Test key» block. The same is done by POST /api/projects/{id}/test-key — it creates the key or re-issues an existing one (calling again rotates the key). The response contains test_api_key — it is shown once, save it right away.