Your customer should know exactly what they are approving.
Connect the right customer, show the proposed action, and resume only after their answer. A Pushary Partner walkthrough with a runnable retry example.

To get a customer's approval for an AI agent action, your backend must identify the customer, show the exact proposal, save the decision and check the answer before executing. Sending a notification is one part of that flow.
We built Partner APIs because this work should not have to start from scratch for every agent product. The customer should understand the decision. The application should remember what the answer authorized.
This walkthrough uses a synthetic EUR 4.80 refund on order demo-4471. It is not a customer case study. The downloadable example checks a decision but never issues a refund.
Start with a proposal someone can actually judge
“Can I continue?” tells the customer almost nothing. State the action, the target and the amount. Put the reason beside the question so the person does not have to reconstruct what the agent was doing.
| Part | Synthetic example |
|---|---|
| Product | Demo Store |
| Agent | Refund assistant |
| Question | Approve the EUR 4.80 refund on order demo-4471? |
| Reason | The delivery charge was duplicated. |
| Effect of approval | Refund EUR 4.80 to the original payment method. |
| Effect of rejection | Keep the refund on hold. |
| Deadline | One hour after the request is created. |
These are proposed details for your application to display and enforce, not a claim that Pushary checks the order or the payment method. Your backend must verify the reason, amount and target before asking. Keep sensitive customer information out of lock-screen copy.
Save an immutable proposal with its operation ID and recipient. If the amount, target or recipient changes, ask about the new proposal. Do not reuse a yes for different work.
Connect the customer who owns the decision
Your server chooses the recipient from the signed-in session. The model does not choose who can approve. Use your application's stable user ID as externalId and check that this person may approve the proposed action.
Enrollment needs a full-access Partner key on your trusted backend. The onboarding runtime key has agent scope: it can create and read decisions, but it cannot enroll users. A recipient-bound key cannot enroll users either. Keep privileged credentials out of browser code, prompts and agent tools.
Using @pushary/server in that authenticated server context:
const enrollment = await enrollmentClient.enroll(user.id)Here, enrollmentClient is the server SDK client holding the enrollment credential, and user is your authenticated application user. Show enrollment.universalLink only to that user as a button or QR code. The Partner setup guide shows how to initialize the client.
Creating the invitation does not connect a device. The person opens it and agrees to receive approvals from your product. Invited customers do not need a Pushary account or subscription.
If they install the app after opening the link, they must open the original link again or use Invited by a company? to scan the QR code. Keep the invitation available while setup is unfinished. The link expires after 24 hours and can be redeemed once per channel, native app and browser push. Treat it as a credential, not a public URL.
Check the delivery route before asking
Call reachability(user.id) from your server before you park work on an answer.
| Result | What your application should do |
|---|---|
push | A registered push channel exists. Create the decision and wait for its answer. |
fallback | The user is named but has no confirmed push channel. Offer enrollment or use a supported alternative delivery path. |
| Read fails | Keep the action on hold and surface the setup or service error. |
A registered device is not a delivery receipt. Pushary cannot infer that a person saw a notification from the existence of an enrolled phone.
For a workflow that requires push, set requireReachable: true when creating the decision. This checks again at creation and returns HTTP 409 with code: "unreachable" when no registered push channel exists. It does not count Slack or a link your application sends itself.
You can instead deliver an approval link through your own service or collect an answer in your own signed-in screen. Those paths require separate credential and identity handling. Follow the delivery options rather than treating a shared link as proof of a person's identity.
Show the action, then save the decision
Your backend creates the confirmation and persists the returned decision ID alongside the proposal. Use a stable idempotency key so a job retry does not ask again within the creation deduplication window.
For a real integration, the customer's notification names your product. On iPhone, a confirmation can be answered from the lock screen. Android opens the app; choices and written answers open the app too. The approval card gives the person the question to answer. Your host still decides whether the protected action can run.
The runnable retry example demonstrates creation and recovery. Install the pinned SDK in a local example directory:
npm install @pushary/server@2.5.0
node retry-approval.mjs operation-demo-4471 customer-demoFor a live decision, set a server-side Partner key as PUSHARY_API_KEY and replace customer-demo with a user enrolled in your test application. That command creates a real approval request when pointed at production. It does not enroll a person or check reachability first. Do not run it against an unsuspecting customer. The account-free fixture below is the way to test the code without contacting anyone.
Save the returned decision ID. After a worker restart, read it instead of creating a replacement:
node retry-approval.mjs operation-demo-4471 customer-demo YOUR_SAVED_DECISION_IDThe script verifies the recipient and decision ID, then requires an answered confirmation yes before reporting approved: true. Even then, it reports actionExecuted: false.
Keep waiting and permission separate
A caller timeout means your process stopped waiting. It does not mean the customer said no, the decision expired or the action may continue.
| Decision state | Protected action |
|---|---|
| Pending | Wait or resume later using the saved ID. |
| Answered confirmation yes | Verify the saved proposal and atomically claim the operation before execution. |
| Answered no | Stop. |
| Expired or cancelled | Stop. |
| Wrong recipient, wrong decision ID or failed read | Stop and reconcile. |
| Free-text answer containing “yes” | Do not treat it as a confirmation. |
Two workers can read the same yes. Your application still needs an atomic claim and the downstream provider's idempotency mechanism. One approval across agent retries explains that boundary and the 24-hour creation deduplication window.
Test the failure paths without contacting a customer
Download retry-approval.mjs and retry-approval.test.mjs into the same directory, install the SDK above, and run:
node --test retry-approval.test.mjsThe suite uses a local HTTP fixture and fresh Node processes. It covers duplicate creation, a later answer, pending, expiry, cancellation, rejection, free text, mismatched recipients, mismatched IDs and a missing saved decision. All ten scenarios passed in our October 15, 2026 check with SDK 2.5.0.
This establishes the example's decision handling against synthetic responses. It does not test enrollment on a real phone, notification delivery, production deduplication or payment execution. For a real-device check, use your own test user and verify invitation consent, approval, rejection, no answer, restart and disconnection before connecting a customer workflow.
Build one complete path: the right person, the exact proposal and an answer your backend enforces. Follow the Partner integration guide to connect that path to your product.
Product behavior was checked against the current integration guide and implementation on October 15, 2026. See our editorial and corrections policy for how we distinguish examples from measured results.