Test your integration
Test approval, denial, timeout, retries, and delivery before enabling live customer actions.
Start with simulated decisions, then test the real channel your customers will use. Keep the action behind approval replaced with a harmless local operation until both pass.
API sandbox
The API sandbox requires a signed-in workspace with a site and permission to act on agents. It does not require the Partner plan. It is separate from the public demo and the Partner dashboard's Send a test approval card.
While signed in to your dashboard, call POST /api/agent/sandbox. For example,
run this in the browser's developer console on your dashboard:
const response = await fetch("/api/agent/sandbox", { method: "POST" })
const sandbox = await response.json()
if (!response.ok) throw new Error(sandbox.error)The response's key is shown once. Copy it into your local server environment as
PUSHARY_API_KEY. Do not paste your dashboard session cookie into scripts.
A 404 means no current site is configured; a 403 means your role cannot act on
agents.
Sandbox keys can access identity, authorize, authorization-rules,
decisions, authorizations, and reachability. They cannot enroll a real
device, send notifications, issue keys, or manage webhook secrets.
Exercise all three answers
After installing @pushary/server, run this script with your sandbox key:
import assert from "node:assert/strict"
import { randomUUID } from "node:crypto"
import { createPusharyServer } from "@pushary/server"
const pushary = createPusharyServer({ apiKey: process.env.PUSHARY_API_KEY! })
for (const outcome of ["approve", "deny", "no_answer"] as const) {
const result = await pushary.decisions.ask({
externalId: "integration-test-user",
question: "Allow this test action?",
type: "confirm",
sandboxOutcome: outcome,
idempotencyKey: randomUUID(),
timeoutMs: 1000,
expiresInSeconds: 60,
})
assert.equal(result.approved, outcome === "approve")
assert.equal(result.answered, outcome !== "no_answer")
assert.equal(result.status, outcome === "no_answer" ? "pending" : "answered")
console.log(outcome, result.decisionId, result.status)
}no_answer leaves the decision open. The one-second SDK wait does not expire it.
Save the ID to check that it becomes expired after its 60-second lifetime, or
cancel it when finished.
Sandbox decisions use the real decision store and record the simulator as the answer source. They send no phone, browser, or Slack notification and use no production notification allowance. They do not prove webhook delivery or any downstream action in your application.
Exercise retries and recovery
| Check | Expected result |
|---|---|
| Repeat create with the same recipient and idempotency key | Same decision ID |
| Retry after HTTP 503 or a failed create | Honor Retry-After when present, reuse the key, and verify the returned ID can be read |
| Race two creates with the same key and recipient | One stored decision and one delivery fan-out |
| Read the saved ID after restarting your worker | Same stored result |
| Leave a decision unanswered until expiry | expired; the action stays blocked |
| Cancel an abandoned decision | cancelled; no action executes |
Use an allow, deny, or unmatched authorization rule | allow, deny, or requires_human respectively |
| Claim the same authorization permit twice | Second claim refused as already_consumed |
Test your workflow's execution count too. A stored approval must not produce two side effects when a webhook or worker job is replayed.
Test live delivery
Use a live Partner key from Agent settings. Use an ID belonging to a test user you control, and complete enrollment before sending the approval.
curl --fail-with-body https://pushary.com/api/v1/server/enroll \
-H "Authorization: Bearer $PUSHARY_API_KEY" \
-H "Content-Type: application/json" \
-d '{"externalId":"integration-test-user"}'Open the returned universalLink and complete one of these paths:
| Channel | What to verify |
|---|---|
| Native app | Install Pushary if needed, open the link, and consent. After a fresh install, use Invited by a company? and scan the retained QR from another screen if the link does not reopen the app. |
| Browser | Choose Enable browser notifications in a supported browser and grant permission. Ordinary iOS Safari tabs cannot receive web push; use the native app or a supported Home Screen web app. |
| Own delivery channel | Create with approvalUrl: true, then send the returned link through your own email, SMS, or inbox service. |
Then create a decision:
curl --fail-with-body https://pushary.com/api/v1/server/decisions \
-H "Authorization: Bearer $PUSHARY_API_KEY" \
-H "Content-Type: application/json" \
-H "Idempotency-Key: integration-test-1" \
-d '{
"externalId":"integration-test-user",
"question":"Allow this test action?",
"type":"confirm",
"requireReachable":true,
"expiresInSeconds":3600
}'Use a new key for each new test. Omit requireReachable if you are testing your
own approval-link delivery rather than push. Save the returned decision ID, answer
on the device, and read it:
curl --fail-with-body \
"https://pushary.com/api/v1/server/decisions/DECISION_ID?wait=30" \
-H "Authorization: Bearer $PUSHARY_API_KEY"Check both "yes" and "no". Only an affirmative answer should release your
test action.
Slack
Connect Slack in your dashboard integrations. A decision with no email goes
to the configured shared channel. An email attempts a DM to a member of that
workspace; lookup or DM failure falls back to the shared channel.
Test that fallback with non-sensitive content. Slack is not included in the end-user push reachability count. Review which team members may see and answer customer requests before enabling this channel.
Webhooks and your own UI
Set your callback URL, retrieve the site's webhookSecret, and use the
verified callback example.
Confirm a real answer reaches your endpoint, reject an invalid signature, and
replay a callback to verify your workflow does not execute twice.
Temporarily make the callback unavailable and confirm polling recovers the answer.
For headless answers, verify that your backend rejects a user trying to answer
another user's decision. If a phone already recorded "no", a later API answer
must preserve that result.
What this proves
The sandbox verifies API behavior and your response handling. Live tests verify enrollment, notification permissions, delivery, and callback configuration. Neither proves an external side effect is safe to retry: use the downstream provider's idempotency controls and your saved workflow state.