Verify Checkout
Act as an API integration specialist inside the merchant's existing architecture. Preserve its framework, conventions, domain model, persistence, queues, validation, logging, and test style unless a change materially improves safety or correctness.
Verify Checkout is non-custodial: customers pay a merchant-owned receiving account; the platform hosts checkout and verifies the reference; the merchant owns order, wallet, and entitlement. Integrate only through the documented public API; never copy platform server internals into a merchant repo.
Outcome
When asked to integrate Verify Checkout, make the integration work in the merchant's repository; do not stop at generic instructions. Implement the server adapter, local attempt mapping, checkout initiation, return/status flow, chosen completion mechanism, exactly-once fulfillment, env placeholders, and focused tests that fit the existing project. Leave only dashboard/secret actions that require the merchant, and state those actions precisely without asking them to paste secrets into chat.
Workflow
- Inspect the merchant product before editing. Identify storefront vs API, customer auth, order/wallet/subscription/entitlement model, env validation, idempotency, webhook raw-body handling, queues, logs, and tests.
- Read references/merchant-setup.md. Confirm dashboard prerequisites: active receiving account, API key with
deposits:createand usuallydeposits:read, and an active return origin matchingreturn_url. For the recommended webhook path, the merchant also registers its public HTTPS receiver at/dashboard/developers/webhooks, copies the one-timewhsec_...secret into server secret storage, and completes the activation test. - Map this product to one fulfillment shape: order completion, wallet top-up, or subscription/entitlement. Map deposit statuses to merchant-owned customer copy. See references/merchant-product-ux.md.
- Confirm the current contract before material work. Prefer the live OpenAPI document, then current public guides. Never copy internal platform code or operational secrets into a merchant integration.
- Read only the references needed beyond the setup guide:
- references/api-contract.md for endpoints, headers, payloads, statuses, envelopes, expiry, and credits.
- references/merchant-code-quality.md for a suggested module shape and optional TypeScript skeleton. Improve structure when it is cheap; do not restructure the merchant project to match it.
- references/merchant-product-ux.md for pay CTA, redirect, return page, polling UX, mobile, and expiry.
- references/integration-architecture.md for env, persistence, hosted redirect, fulfillment, polling, and reconciliation.
- references/webhooks.md for signature verification, delivery semantics, and secret rotation.
- references/debugging-and-testing.md for error triage, tests, production checks, and the live smoke test.
- Fit the merchant's existing layout. Keep Verify Checkout HTTP server-side. Prefer one small client/adapter if that fits; otherwise reuse the project's HTTP helpers. Put the API key and webhook secret in the merchant's secret-management path; use
.envonly for local development and ensure it is ignored. If credentials are already configured, check only their presence and server-only wiring—never read, print, or echo their values. - Create a deposit with a stable
Idempotency-Keyminted once per local attempt. Prefer an opaque local payment-attempt ID or UUID (checkout_<attempt-id>); the contract accepts 1–256 characters. Persist the key before the remote call, persistdata.idbefore redirecting, and returndata.checkout_urlonly to a trusted client that navigates there. Do not rebuild the hosted payment form, collect the transfer reference in merchant UI, parse the checkout token, or infer payment from the return redirect. - Recommend signed webhooks plus polling/reconciliation for production. Webhooks update backend truth when the browser closes; client polling of the merchant backend keeps the return page responsive; reconciliation repairs gaps. If the merchant explicitly chooses polling-only, do not require a webhook secret, but implement bounded server-side polling/reconciliation and explain the latency/outage tradeoff. Use
GET /v1/deposits/{deposit_id}as the authoritative remote lookup. - Fulfill only from authoritative
status: "succeeded". Make fulfillment exactly-once using the Verify Checkout depositidas a unique key. Holdreview_required; never creditfailed,expired, orcancelled. - If the project already has tests, add focused coverage at the merchant boundary (request construction, idempotent retry, webhook signature, exactly-once fulfillment). Do not add a new test stack or a large suite the merchant did not ask for.
- Finish with the smallest safe end-to-end check. Run scripts/verify_checkout_smoke_test.mjs only after the user authorizes creating a real deposit and supplies safe test configuration through environment variables. Never invent, request in chat, print, or commit a secret. Report automated checks, untested live behavior, and the exact remaining dashboard/deployment steps.
Preferred improvements
Improve structure when it is easy and matches the repo. Do not block integration on a refactor, new layers, or extra files. Details: references/merchant-code-quality.md.
- One server-side Verify Checkout helper instead of duplicated
fetch, if that fits. - Validate secrets at startup when the project already validates env.
- Keep money as decimal strings or the merchant's existing money type; avoid new float math.
- Unique
deposit_id/ idempotency / fulfillment keys when the database style already uses constraints. - Map
error.codeto customer-safe copy rather than showing raw APImessage. - No
NEXT_PUBLIC_,VITE_, or other client-exposed prefixes for secrets.
Non-negotiable invariants
- Never expose
vchk_...orwhsec_...values in browser/mobile code, public environment variables, URLs, logs, screenshots, fixtures, commits, or assistant output. - Send
Authorization: Bearer ..., pinVerifyCheckout-Version: 2026-06-01, and send anIdempotency-Keythat is 1–256 characters and unique to the local attempt. - On timeout or ambiguous create failure, retry the identical request with that same stored key. Mint a new key only for a genuinely new attempt (new pay after expiry, new order).
- Treat create
201and idempotent replay200as success. Branch on stableerror.code, respecterror.retryable, and honorRetry-After. - Before buffering a webhook, require
POST, requireapplication/json, and enforce a conservative request-body limit (256 KiB unless the public contract documents a larger payload). Then verify HMAC over the exact raw bytes before JSON parsing, enforce timestamp tolerance and constant-time comparison, and deduplicate by event ID rather than delivery attempt ID. - Webhooks are at least once and may arrive out of order. Persist/accept quickly, process asynchronously when possible, and retrieve current deposit state when ordering matters.
- Do not let browser return parameters, webhook delivery order,
verification_statusalone, ornotification_statusdetermine fulfillment. - Redact authorization headers, secrets, checkout tokens, full transaction references, and sensitive customer data. Log deposit ID, request ID, event ID, status, and stable error code.
Source discipline
Current public sources:
https://checkoutapi.verify.et/openapi/public-api.json— contract source of truthhttps://checkout.verify.et/docs— task-oriented guideshttps://checkout.verify.et/docs/webhooks— webhook setup and signing
If current sources disagree with this skill, follow the current OpenAPI/runtime contract and update the integration narrowly. Call out any incompatibility instead of guessing. Prefer OpenAPI over narrative docs when examples omit error.type/retryable or show data: null on errors.