Skip to main content
Sell digital goods, subscriptions, or licenses from a native app. The app owns UX. A small backend-for-frontend (BFF) you own holds the Shoppex API key and talks to /dev/v1/*. Payment finalization happens in the system browser and returns via deep link.
If you are selling in-app digital content that is consumed inside the app, Apple App Store rules may require In-App Purchase. This pattern fits physical-feeling deliverables (license keys, external downloads, Discord roles, gaming keys) and subscriptions that unlock an external service. Check the current review guidelines before shipping.

Why this works well

No API key on device

The shx_* key stays on your BFF. The app only ever sees short-lived payment-session URLs.

Familiar auth shape

Your app keeps its own login (OAuth, magic-link, social). Shoppex is only commerce.

Flow

  1. The app asks your BFF to buy a product.
  2. Your BFF creates the payment in Shoppex.
  3. The app opens the hosted checkout URL in the system browser.
  4. Shoppex redirects back to your universal link or return page.
  5. Your BFF receives the signed webhook and updates the buyer state.
  6. The app shows the final state after polling or receiving a push notification.

Shape

PieceResponsibility
Native appUI, auth, deep-link handler, optimistic “Processing…” state
Your BFFExchange user session for Dev API calls, receive webhooks, push to device
ShoppexCatalog, payment session, PSP orchestration, webhook emission
System browser / ASWebAuthenticationSessionThe payment UI itself (required by PSP compliance)

Key choices

Universal links (iOS) / app links (Android) hand control back to your app without the “Open in app?” prompt and survive cold starts better than myapp://:
return_url: https://yourapp.com/return
cancel_url: https://yourapp.com/cancel
Your universal link route detects whether the app is installed and either deep-links or falls back to a web “open the app” screen.

Use ASWebAuthenticationSession / Chrome Custom Tabs

Do not embed checkout in a WKWebView — PSPs may block it, 3DS flows may break, and Apple rejects apps that proxy payment forms. Use the system-provided authenticated browser surface.

Optimistic UI

After the deep-link comes back, the app should show “Finalizing…” and poll GET /dev/v1/payments/{uniqid} until status === 'COMPLETED'. The webhook then triggers a push notification for confirmation. Both paths converge on the same order state.

Pitfalls

  • Deep-link replay attacks — treat the deep-link return as a hint, not proof of payment. Always confirm state via Dev API call or webhook before granting access.
  • App Store review — Apple may reject apps that sell digital goods consumed in-app without IAP. External services, off-device benefits, or fulfillment outside the app are typically the safer fit, but you should still validate the exact flow before launch.
  • Push notifications lag — webhooks arrive at your BFF in milliseconds, but APNs / FCM delivery can lag. The app’s polling covers the gap.

Architecture Reference

Setup C (Mobile + BFF) — full diagram and responsibility split.

Webhook Security

HMAC-SHA512, retries, constant-time verification.