---
title: SDKs (Web and Mobile) Integration Best Practices
slug: sdks/best-practices
excerpt: Learn the best practices to follow before beginning with the SDK integration.
hidden: false
sidebar_order: 2
metadata:
  title: SDK Integration Best Practices | Pine Labs
  description: >-
    SDK integration best practices for Pine Labs: improve security, performance,
    reliability and UX across web, Android and iOS payment integrations.
sidebar_label: SDKs Best Practices
---
<div className="pill-tabs">
<Tabs>
<Tab label="Android">

<div className="px-4">

## Android SDKs Best Practices

<CardGrid columns="3">
<Card icon="link" title="One-Time Token Per Attempt" description="Generate the checkout token or redirect URL on your backend per order attempt and pass it to the SDK only when the user is ready to pay. Never reuse old tokens across retries." />
<Card icon="workflow" title="Handle All Three Callbacks" description="Handle success, error, and cancel explicitly in your app flow so users are never stuck in an ambiguous state. Reference: ExpressSDKCallback.kt" />
<Card icon="server" title="Server-Authoritative Order State" description="Use the SDK callback as a client signal only. Confirm final payment status on your backend via order-status API, webhook, or reconciliation before marking an order paid." />
<Card icon="lock" title="Never Log Sensitive Data" description="Do not log full tokens, customer identifiers, card/UPI details, or OTPs. Keep logs masked and environment-aware (debug vs release builds)." />
<Card icon="settings" title="Separate Sandbox and Production" description="Use a strict config switch for sandbox and production environments. Never hardcode sandbox mode in release builds." />
<Card icon="zap" title="Lifecycle-Safe UI Integration" description="If a callback updates UI, guard against dead activity or fragment state. Route users to a robust order-status screen after payment." />
</CardGrid>

</div>

</Tab>

<Tab label="iOS">

<div className="px-4">

## iOS SDKs Best Practices

<CardGrid columns="3">
<Card icon="code" title="Use a Single SDK Initialization" description="Initialize the SDK only once during your app lifecycle. Re-initializing multiple times can cause unexpected behaviour or runtime errors." />
<Card icon="check" title="Match Swift & iOS Compatibility" description="The SDK is built in Swift 5. Ensure your app targets Swift 5 or later and keep your iOS deployment target up-to-date to avoid build errors." />
<Card icon="shield-check" title="Network & Security Configuration" description="Allowlist required domains and configure App Transport Security (ATS) properly. Common issue in simulators or corporate networks with SSL restrictions." />
<Card icon="lock" title="Handle API Keys Securely" description="Never hardcode API keys in the app bundle. Generate and supply them securely from your backend to prevent exposure and misuse." />
<Card icon="refresh-cw" title="Keep SDK Updated" description="Right-click the package name and click Update Package to get the latest security patches, new payment methods, and compatibility fixes." />
</CardGrid>

## Recommended Way to Start the SDK

<Callout type="info">
Start the SDK from a **fully presented, visible view controller** that is part of your app's normal navigation flow.

Trigger the SDK launch via a user-initiated action, such as tapping a **"Pay Now"** button.

Once launched, the SDK manages its entire internal navigation using its own navigation stack.
</Callout>

### Ideal Entry Points

<div className="not-prose card-grid-2">
 <div className="card-grid-item">
 <span className="inline-flex items-center justify-center w-10 h-10 rounded-lg bg-[#D0F6E5]/60 mb-2"><svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="#003434" strokeWidth="1.8" strokeLinecap="round" strokeLinejoin="round"><circle cx="12" cy="12" r="9"/><path d="m8.5 12.5 2.3 2.3 4.7-4.8"/></svg></span>
    <h4>Ideal Entry Points</h4>
    <p>The SDK should be started from:</p>
  <ul class="entry-list">
                        <li class="entry-item">
                        
                            <span>Checkout screen</span>
                        </li>
                        <li class="entry-item">
                        
                            <span>Order summary screen</span>
                        </li>
                        <li class="entry-item">
                           
                            <span>Payment selection screen</span>
                        </li>
                    </ul>
                    <p>These screens are stable, full-screen, and suitable for presenting the SDK.</p>
  </div>
 <div className="card-grid-item">
<span className="inline-flex items-center justify-center w-10 h-10 rounded-lg bg-[#D0F6E5]/60 mb-2"><svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="#003434" strokeWidth="1.8" strokeLinecap="round" strokeLinejoin="round"><path d="M12 3 21 20H3L12 3Z"/><path d="M12 9v5"/><circle cx="12" cy="17" r="0.8" fill="#003434"/></svg></span>
  <h4>What to Avoid</h4>
  <p>Do not start the SDK from:</p>
<ul class="entry-list">
                        <li class="entry-item">
                            
                            <span>Alerts (UIAlertController)</span>
                        </li>
                        <li class="entry-item">
                     
                            <span>Bottom sheets or half sheets (.pageSheet)</span>
                        </li>
                        <li class="entry-item">
                        
                            <span>Popups or transient modals</span>
                        </li>
                        <li class="entry-item">
                           
                            <span>Views that are already being presented or dismissed</span>
                        </li>
                    </ul>
</div>

</div>




--- 
<Callout type="warning" title="Launching from incorrect contexts can cause:">
- Presentation warnings
- Broken navigation transitions
- Improper SDK dismissal
- Unexpected runtime behavior
</Callout>

## Navigation Ownership

| Responsibility | Owner |
|---|---|
| Presenting the SDK | Merchant app |
| Creating its own navigation controller | SDK |
| Pushing all SDK screens | SDK |
| Handling retry and error flows | SDK |
| Dismissing itself when the flow ends | SDK |

<Callout type="warning">
Merchants should **not** push SDK screens or attempt to control SDK navigation.
</Callout>

## SDK Initialization Rules

<Steps>
<Step number={1} title="One launch per payment attempt">
Initialize and launch the SDK only once per payment attempt. Do not re-initialize mid-flow.
</Step>
<Step number={2} title="Valid token required before launch">
Ensure a valid order token is available from your backend before starting the SDK.
</Step>
<Step number={3} title="No parallel launches">
Avoid triggering multiple simultaneous SDK launches — guard the launch with a state flag.
</Step>
</Steps>
</div>

</Tab>

<Tab label="Flutter">
<div className="px-4">
## Flutter SDK Best Practices

<CardGrid columns="3">
<Card icon="check" title="Start from a Stable Screen" description="Launch from a fully visible screen in your normal navigation flow, such as checkout, order summary, or payment selection." />
<Card icon="server" title="Always Verify on Backend" description="Treat PaymentResult as a client signal only. Confirm final status from your backend using the Order Inquiry API before fulfillment." />
<Card icon="workflow" title="Single SDK Call Per Attempt" description="Call startPayment or startPaymentWithRedirect once per payment attempt. Wait for PaymentResult before retrying." />
<Card icon="info" title="Handle All Statuses" description="Handle success, failure, cancelled, and invalid_request with clear UI actions so users are never stuck." />
<Card icon="refresh-cw" title="Use Fresh Redirect URL" description="Tokens and redirect URLs are short-lived. Generate a fresh order and redirect_url for every new attempt." />
<Card icon="settings" title="Keep SDK Updated" description="Stay on the latest SDK version to receive security fixes, payment method updates, and bug fixes." />
</CardGrid>

## Launch Context Guidance

<Callout type="info" title="Recommended launch points">
Trigger SDK launch from stable full-screen routes via explicit user action (for example, tapping **Pay Now**).
</Callout>

| Recommended | Avoid |
|---|---|
| Checkout screen | Dialog boxes or `showDialog()` |
| Order summary screen | Bottom sheets or `showModalBottomSheet()` |
| Payment selection screen | Screens currently being pushed or popped |
| User-initiated action | Auto-launch from `initState()` or `build()` |

<Callout type="warning" title="Incorrect launch contexts can cause:">
Navigation conflicts, unstable transitions, and unexpected behavior.
</Callout>

## Payment Result Handling

| Status | What to show |
|---|---|
| `success` | Order confirmation screen. Also verify on backend. |
| `failure` | Error message with a **Retry** option. |
| `cancelled` | Message: "Payment was not completed" with **Try Again**. |
| `invalid_request` | Developer error; log the `message` field for debugging. |

## Runtime and Security Safeguards

<Steps>
<Step number={1} title="Verify payment server-side">
Protect against callback interruption, client-side tampering, and WebView-close edge cases by confirming final status from your backend.
</Step>
<Step number={2} title="Do not modify SDK WebView">
Do not inject JavaScript, intercept navigation, or overlay custom UI on the payment WebView.
</Step>
<Step number={3} title="Test UPI on real devices">
UPI intent flows need physical devices with payment apps installed. Final validation must be done on real Android and iPhone devices.
</Step>
</Steps>

## Update SDK Version

```yaml
dependencies:
	pine_payment_sdk: ^1.0.0
```

```bash
flutter pub get
```
</div>
</Tab>


<Tab label="React Native">

<div className="px-4">

## React Native SDKs Best Practices

<CardGrid columns="3">
<Card icon="link" title="One-Time Token Per Attempt" description="Generate a fresh checkout redirect URL on your backend per order and never hardcode tokens in your app code." />
<Card icon="server" title="Server-Authoritative Order State" description="Treat backend payment enquiry as the final source of truth before marking an order as success." />
<Card icon="workflow" title="Idempotent Order Updates" description="Implement idempotency in your order update API so repeated callbacks do not create duplicate fulfillment." />
<Card icon="check" title="Handle All Callback Branches" description="Handle success, error reason, and error status explicitly so users are never stuck in an ambiguous state." />
<Card icon="lock" title="Never Log Sensitive Data" description="Log callback events with correlation IDs (order, transaction, session), but redact sensitive values like tokens and card details." />
<Card icon="zap" title="Timeout & Retry Logic" description="Implement timeout and retry logic around your backend enquiry call to prevent stuck checkout states." />
<Card icon="refresh-cw" title="Keep Dependencies Updated" description="Maintain react-native-webview within the supported version range during upgrades to avoid compatibility issues." />
<Card icon="shield-check" title="Network & Security Validation" description="Validate network and security policies so checkout URLs and redirects are not blocked on enterprise or restricted networks." />
<Card icon="check" title="Comprehensive Testing" description="Test the full payment flow in staging and production-like environments: success, failure, cancellation, app-switch, and offline scenarios." />
</CardGrid>

## React Mobile SDK Deployment Best Practices

<div className="not-prose card-grid-2">
  <div className="card-grid-item">
  <span class="inline-flex items-center justify-center w-[38px] h-[38px] rounded-lg bg-[#D0F6E5] dark:bg-[#003434] mb-2"><svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-server w-5 h-5 text-[#003434] dark:text-[#D0F6E5]" aria-hidden="true"><rect width="20" height="8" x="2" y="2" rx="2" ry="2"></rect><rect width="20" height="8" x="2" y="14" rx="2" ry="2"></rect><line x1="6" x2="6.01" y1="6" y2="6"></line><line x1="6" x2="6.01" y1="18" y2="18"></line></svg></span>
  <h3>Use the Appropriate Environment</h3>

  <p>
    Ensure that your application is configured to use the appropriate
    environment for each stage of development.
  </p>

  <p><strong>Recommendations</strong>:</p>

  <ul>
    <li>Use the production backend APIs for live transactions.</li>
    <li>
      Configure the SDK with <code>environment: 'prod'</code> when connecting
      to the production environment.
    </li>
    <li>
      Ensure that the SDK environment matches the order token generated by your
      backend.
    </li>
  </ul>
</div>
  <div className="card-grid-item">
    <span class="inline-flex items-center justify-center w-[38px] h-[38px] rounded-lg bg-[#D0F6E5] dark:bg-[#003434] mb-2"><svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-lock w-5 h-5 text-[#003434] dark:text-[#D0F6E5]" aria-hidden="true"><rect width="18" height="11" x="3" y="11" rx="2" ry="2"></rect><path d="M7 11V7a5 5 0 0 1 10 0v4"></path></svg></span>
    <h3>Protect Sensitive Credentials</h3>

  <p>
    Keep all merchant credentials and sensitive information on secure backend systems.
  </p>

  <p><strong>Recommendations</strong>:</p>

  <ul>
    <li>Never store merchant credentials or API secrets within the React Native application.</li>
    <li>
      Generate order tokens only from your backend.
    </li>
    <li>
      Expose only the order token to the mobile application.
    </li>
  </ul>
  </div>
  <div className="card-grid-item">
    <span class="inline-flex items-center justify-center w-10 h-10 rounded-lg bg-[#D0F6E5]/60 mb-2"><svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="#003434" stroke-width="1.8" stroke-linecap="round" stroke-linejoin="round"><path d="M12 2L2 7l10 5 10-5-10-5z"></path><path d="M2 17l10 5 10-5"></path><path d="M2 12l10 5 10-5"></path></svg></span>
    <h3>Test All Payment Outcomes</h3>

  <p>
    Validate your application against all supported payment scenarios before releasing it to production.
  </p>

  <p><strong>Test the following scenarios</strong>:</p>

  <ul>
    <li>Successful payment.</li>
    <li>
      Failed payment.
    </li>
    <li>
      User cancellation.
    </li>
    <li>
      User closes or dismisses the payment screen.
    </li>
  </ul>
  <p>Ensure that your application handles each scenario gracefully and provides appropriate feedback to the customer.</p>
  </div>
  <div className="card-grid-item">
    <span class="inline-flex items-center justify-center w-[38px] h-[38px] rounded-lg bg-[#D0F6E5] dark:bg-[#003434] mb-2"><svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-circle-check-big w-5 h-5 text-[#003434] dark:text-[#D0F6E5]" aria-hidden="true"><path d="M21.801 10A10 10 0 1 1 17 3.335"></path><path d="m9 11 3 3L22 4"></path></svg></span>
    <h3>Validate Production Builds</h3>

  <p>
    Before publishing your application, verify that your release builds are correctly configured.
  </p>

  <p><strong>Recommendations</strong>:</p>

  <ul>
    <li>Sign Android release builds using the appropriate signing configuration.</li>
    <li>
      Sign iOS release builds using the correct provisioning profiles and certificates.
    </li>
    <li>
      Perform end-to-end testing on physical Android and iOS devices using the production environment.
    </li>
  </ul>
  </div>
  <div className="card-grid-item">
    <span class="inline-flex items-center justify-center w-[38px] h-[38px] rounded-lg bg-[#D0F6E5] dark:bg-[#003434] mb-2"><svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-circle-check-big w-5 h-5 text-[#003434] dark:text-[#D0F6E5]" aria-hidden="true"><path d="M21.801 10A10 10 0 1 1 17 3.335"></path><path d="m9 11 3 3L22 4"></path></svg></span>
    <h3>Verify Payment Status on the Backend</h3>

  <p>
    Always confirm the final payment status before fulfilling an order.
  </p>

  <p><strong>Recommendations</strong>:</p>

  <ul>
    <li>Verify the transaction status using your backend APIs.</li>
    <li>
      Do not rely solely on the client-side payment callback to determine payment success.
    </li>
  </ul>
  </div>
</div>


</div>

</Tab>


</Tabs>



## Related resources

<div className="not-prose card-grid-3">
  <div className="card-grid-item">
    <div className="inline-flex items-center justify-center w-10 h-10 rounded-lg bg-[#D0F6E5]/60 mb-3">
      <svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="#003434" strokeWidth="1.8" strokeLinecap="round" strokeLinejoin="round"><circle cx="12" cy="12" r="10"/><line x1="2" y1="12" x2="22" y2="12"/><path d="M12 2a15.3 15.3 0 0 1 4 10 15.3 15.3 0 0 1-4 10 15.3 15.3 0 0 1-4-10 15.3 15.3 0 0 1 4-10z"/></svg>
    </div>
    <h3>Web SDK</h3>
    <p>Learn how you can integrate with Pine Labs Web SDK across all platforms.</p>
    <div className="flex flex-wrap gap-2 my-2">
      <a href="../sdks/web-sdks/android" className="text-xs font-medium px-2.5 py-1 rounded-full bg-[#D0F6E5]/60 text-[#003434] no-underline hover:bg-[#D0F6E5]">Android</a>
      <a href="../sdks/web-sdks/flutter" className="text-xs font-medium px-2.5 py-1 rounded-full bg-[#D0F6E5]/60 text-[#003434] no-underline hover:bg-[#D0F6E5]">Flutter</a>
      <a href="../sdks/web-sdks/ios" className="text-xs font-medium px-2.5 py-1 rounded-full bg-[#D0F6E5]/60 text-[#003434] no-underline hover:bg-[#D0F6E5]">iOS</a>
      <a href="../sdks/web-sdks/react" className="text-xs font-medium px-2.5 py-1 rounded-full bg-[#D0F6E5]/60 text-[#003434] no-underline hover:bg-[#D0F6E5]">React Native</a>
    </div>
<br/>
    <a href="../sdks/web-sdks">Explore Web SDK →</a>
  </div>
  <div className="card-grid-item">
    <div className="inline-flex items-center justify-center w-10 h-10 rounded-lg bg-[#D0F6E5]/60 mb-3">
      <svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="#003434" strokeWidth="1.8" strokeLinecap="round" strokeLinejoin="round"><rect x="5" y="2" width="14" height="20" rx="2" ry="2"/><line x1="12" y1="18" x2="12.01" y2="18"/></svg>
    </div>
    <h3>Mobile SDK</h3>
    <p>Learn how you can integrate with Pine Labs Mobile SDK across all platforms.</p>
    <div className="flex flex-wrap gap-2 my-2">
      <a href="../sdks/mobile-sdks/android" className="text-xs font-medium px-2.5 py-1 rounded-full bg-[#D0F6E5]/60 text-[#003434] no-underline hover:bg-[#D0F6E5]">Android</a>
      <a href="../sdks/mobile-sdks/flutter" className="text-xs font-medium px-2.5 py-1 rounded-full bg-[#D0F6E5]/60 text-[#003434] no-underline hover:bg-[#D0F6E5]">Flutter</a>
      <a href="../sdks/mobile-sdks/ios" className="text-xs font-medium px-2.5 py-1 rounded-full bg-[#D0F6E5]/60 text-[#003434] no-underline hover:bg-[#D0F6E5]">iOS</a>
      <a href="../sdks/mobile-sdks/react" className="text-xs font-medium px-2.5 py-1 rounded-full bg-[#D0F6E5]/60 text-[#003434] no-underline hover:bg-[#D0F6E5]">React Native</a>
    </div>
<br/>
    <a href="../sdks/mobile-sdks">Explore Mobile SDK →</a>
  </div>
  <div className="card-grid-item">
    <div className="inline-flex items-center justify-center w-10 h-10 rounded-lg bg-[#D0F6E5]/60 mb-3">
      <svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="#003434" strokeWidth="1.8" strokeLinecap="round" strokeLinejoin="round"><rect x="2" y="2" width="20" height="8" rx="2" ry="2"/><rect x="2" y="14" width="20" height="8" rx="2" ry="2"/><line x1="6" y1="6" x2="6.01" y2="6"/><line x1="6" y1="18" x2="6.01" y2="18"/></svg>
    </div>
    <h3>Server SDK</h3>
    <p>Learn how you can integrate with Pine Labs Server SDK across all platforms.</p>
    <div className="flex flex-wrap gap-2 my-2">
      <a href="../sdks/server-sdks/nodejs" className="text-xs font-medium px-2.5 py-1 rounded-full bg-[#D0F6E5]/60 text-[#003434] no-underline hover:bg-[#D0F6E5]">Node.js</a>
      <a href="../sdks/server-sdks/python" className="text-xs font-medium px-2.5 py-1 rounded-full bg-[#D0F6E5]/60 text-[#003434] no-underline hover:bg-[#D0F6E5]">Python</a>
      <a href="../sdks/server-sdks/php" className="text-xs font-medium px-2.5 py-1 rounded-full bg-[#D0F6E5]/60 text-[#003434] no-underline hover:bg-[#D0F6E5]">PHP</a>
      <a href="../sdks/server-sdks/java" className="text-xs font-medium px-2.5 py-1 rounded-full bg-[#D0F6E5]/60 text-[#003434] no-underline hover:bg-[#D0F6E5]">Java</a>
      <a href="../sdks/server-sdks/ruby" className="text-xs font-medium px-2.5 py-1 rounded-full bg-[#D0F6E5]/60 text-[#003434] no-underline hover:bg-[#D0F6E5]">Ruby</a>
      <a href="../sdks/server-sdks/go" className="text-xs font-medium px-2.5 py-1 rounded-full bg-[#D0F6E5]/60 text-[#003434] no-underline hover:bg-[#D0F6E5]">Go</a>
      <a href="../sdks/server-sdks/dotnet" className="text-xs font-medium px-2.5 py-1 rounded-full bg-[#D0F6E5]/60 text-[#003434] no-underline hover:bg-[#D0F6E5]">.NET</a>
    </div>
    <a href="../sdks/server-sdks">Explore Server SDK →</a>
  </div>
</div>


</div>
