Sandbox Setup
This page is the prerequisite for the Advanced Checkout Guide. It walks you through getting credentials, choosing a test merchant, and validating your environment with a curl call.
What you need from Firmly
Email firmlydocs@firmly.ai and ask for:
- A sandbox App ID (
_appId) — a UUID scoped to a non-production environment - A test merchant domain — typically something like
staging.luma.gift - Optional: a server-to-server secret — if you plan to use S2S auth instead of browser sessions
- Optional: an Agentic Pay API token — if you plan to enroll cards and pay with network tokens. See Agentic Pay in the sandbox
Firmly will provision these and confirm the test merchant has a non-empty catalog you can search and buy from.
Endpoints
| Surface | Host |
|---|---|
| General API (auth, discovery, cart, checkout, orders) | https://api.firmly.work |
| Payment (public key, place-order) | https://cc.firmly.work |
These are the sandbox base URLs. Production runs on different hosts from the sandbox. Firmly provides your production base URLs at go-live — plan for a configuration change, not just a new App ID. Going live changes your App ID, your merchants, and your base URLs. For the embedded / hosted checkout drop-in URL, contact Firmly.
Test cards
Firmly accepts these public sandbox cards against test merchants:
| Card number | Brand | What it tests |
|---|---|---|
4111111111111111 |
Visa | Successful payment |
4000000000000002 |
Visa | Generic decline |
4000000000009995 |
Visa | Insufficient funds decline |
4000000000000069 |
Visa | Expired card decline |
Use any future date (e.g. 12 / 2030), any 3-digit CVV, and any cardholder name. These cards only work against sandbox merchants — real merchants reject them.
Verify your setup with curl
Once you have your _appId and a test merchant domain, run these three calls. If all three succeed, you’re ready to run the Advanced Checkout Guide.
1. Bootstrap a session
curl -X POST https://api.firmly.work/api/v1/browser-session \-H "x-firmly-app-id: YOUR_APP_ID"
YOUR_APP_ID is the UUID Firmly provisioned for you — it looks like xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx, not a short slug. Substitute the exact value Firmly sent.
Expected: a JSON response with access_token, device_id, expires_in. Save the access_token as $TOKEN.
2. Search the test merchant catalog
Scope the search to your paired test merchant with filters.domains so you’re verifying that specific merchant’s catalog:
curl -X POST https://api.firmly.work/api/v1/discovery/search \-H "x-firmly-authorization: $TOKEN" \-H "Content-Type: application/json" \-d '{"query": "gift", "filters": {"domains": ["staging.luma.gift"]}, "page_size": 3}'
Expected: a JSON response with a products array. Each product carries a domain field — it should match the test merchant domain you queried (staging.luma.gift above).
Empty products array? Ask Firmly to load sample products for your test merchant.
3. Get the payment public key
curl "https://cc.firmly.work/api/v1/payment/key?format=JWK"
Expected: an RSA JWK with kty, n, e, and a kid (key ID). No auth needed for this endpoint.
If all three return 200s with sensible bodies, your sandbox is live.
Common setup errors
| What you see | Why | Fix |
|---|---|---|
401 on browser-session |
App ID isn’t recognized in this environment | Confirm you’re using the sandbox App ID, not production |
404 ucp_disabled on search |
Merchant hasn’t opted into the relevant surface | Ask Firmly to enable it for the test merchant |
404 DomainNotFound |
Domain typo or wrong environment | Verify the exact domain string from Firmly |
Empty products array |
Merchant has no products in catalog | Ask Firmly to load sample products into the test merchant |
Network failure on cc.firmly.work |
Outbound HTTPS to payment domain is blocked | Add cc.firmly.work to your allowlist alongside api.firmly.work |
400 InvalidAPIToken on /wallets/agentic-pay/* |
You sent the browser-session access_token (or a production token) instead of the Agentic Pay API token |
Use the Agentic Pay API token Firmly issued for the sandbox in x-firmly-authorization |
409 PaymentMethodNotAvailable on /enroll |
The card’s network is not enabled for your destination, or the card is not a network sandbox test card | Ask Firmly to enable the network on your token; enroll only the network sandbox test cards Firmly provided |
Agentic Pay in the sandbox
Agentic Pay — enrolling a cardholder’s card and paying with a network token — runs on the sandbox hosts listed above (api.firmly.work for enrollment and intent, cc.firmly.work for order placement) against the test merchant. It needs one more credential and different test cards.
What you need
- An Agentic Pay API token — sent as
x-firmly-authorizationon the/api/v1/wallets/agentic-pay/*endpoints. This is an app-level token, not the browser-sessionaccess_token. Request it from firmlydocs@firmly.ai together with your sandbox App ID, and say which card networks you want enabled (Visa, Mastercard, Discover). Networks are enabled per destination; a card on a network that is not enabled returns409 PaymentMethodNotAvailable. - Network sandbox test cards — enrollment tokenizes the card at the card network’s sandbox, so the generic test cards above are not enrollable there. Firmly provides network sandbox test cards for each enabled network along with your token.
- Your sandbox App ID and a device session — still required. Order placement (
/wallet-complete-order) is authenticated with the device session token from Browser Session, exactly as for card payments.
Verify your token with curl
Enrolling a card is the quickest proof that the token is valid and the network is enabled. Use a network sandbox test card Firmly provided:
curl -s -X POST https://api.firmly.work/api/v1/wallets/agentic-pay/enroll \-H "x-firmly-authorization: $AGENTIC_PAY_TOKEN" \-H "Content-Type: application/json" \-d '{"pan": "<network sandbox test card>", "expiry_month": "12", "expiry_year": "2030", "cvv": "123", "consumer": {"email": "cardholder@example.com", "country_code": "US"}}'
Expected: 200 with a flow_token, a virtual_card_id, and verification_methods (empty for Discover). A 400 InvalidAPIToken means the token is wrong for this environment; a 409 PaymentMethodNotAvailable means the network is not enabled for your destination or the card is not a sandbox test card.
Browser pieces in the sandbox
Visa and Mastercard verification runs in a network-hosted iframe in the cardholder’s browser. In the sandbox those iframes are served from the networks’ sandbox hosts, and the result comes back through Firmly’s Iframe Callback bridge on api.firmly.work. If you use the Headless SDK, configure it for the sandbox as:
baseUrl—https://api.firmly.workiframeOrigins— the Firmly bridge origin plus the network sandbox iframe origins. These are the values Firmly’s wallet service ships for the sandbox and injects into the hosted SDK bundle (GET https://api.firmly.work/api/v1/wallets/sdk/js?appId=<your App ID>, aswindow.__FIRMLY_AGENTIC_PAY_CONFIG__.iframeOrigins); load that bundle, or copy its values, rather than hard-coding hosts — currentlyhttps://api.firmly.work,https://sandbox.src.mastercard.com,https://sbx.vts.auth.visa.combridgeUrl—https://api.firmly.work/api/v1/wallets/agentic-pay/iframe-callbackvaultBaseUrl—https://cc.firmly.workmerchantDomain—staging.luma.gift
Discover has no iframe step, so a Discover enrollment and intent can be exercised entirely with curl.
Run the full flow
The Agentic Pay Integration guide walks the complete chain — enroll → verify → intent → order — against this sandbox and the test merchant. Create the cart first (session → add line item → set shipping) exactly as in the Advanced Checkout Guide; the Agentic Pay steps replace only the JWE card payment.
Going from sandbox to production
When you’re ready to go live:
- Request a production App ID from Firmly
- Confirm the live merchants you’ll transact against — a production App ID is typically scoped to a specific set of merchants
- Stop using the test cards above; production requires real cards or wallet-based payments
- If you use Agentic Pay, request a production Agentic Pay API token and switch the SDK
iframeOriginsto the networks’ production hosts, which Firmly provides at go-live
Going live is a configuration change for your application, plus the Firmly-side onboarding of any production merchants. There’s no separate “production SDK” to install.
Next steps
- Advanced Checkout Guide — run the full purchase flow
- Authentication — auth model in depth
- Cart lifecycle — cart states and what’s allowed when