---
title: "App Review Quality Requirements | Tajo"
description: "Quality, security, UX, and legal standards your Stripe App must meet to pass review"
url: https://tajo.io/docs/stripe-apps/review-requirements/
markdown: https://tajo.io/docs/stripe-apps/review-requirements.md
lang: en-US
author: "Set Noa"
author_url: https://tajo.io/set-noa/
---

# App Review Quality Requirements

[Set Noa](https://tajo.io/set-noa/)

Stripe reviews every app submitted to the marketplace against a comprehensive set of quality requirements. Understanding these standards before development saves time and reduces the number of review iterations.

Caution

**Status: planned, not built.** Tajo has no Stripe App, so nothing in this section can be installed today. The app identifiers, URLs and credentials on this page are illustrative placeholders, not live endpoints.

## Overview

The Stripe App review evaluates your app across six key areas:

1. **Transparent Pricing**, Clear communication of costs
2. **App Functionality**, Reliability and completeness
3. **Developer Standards**, Code quality and API usage
4. **UX Quality**, User interface and experience standards
5. **Security**, Data protection and secure practices
6. **Legal Compliance**, Privacy and regulatory requirements

## Transparent Pricing

Your app must clearly communicate all costs to users:

- **Pricing disclosure**: All pricing must be stated upfront in the marketplace listing
- **No hidden fees**: Users must not encounter unexpected charges after installation
- **Trial terms**: If offering a trial, clearly state the duration and what happens after it ends
- **Upgrade flows**: Any upsell or upgrade prompts must be non-intrusive and clearly optional
- **Currency**: Display prices in the user’s local currency when possible

Caution

Apps that obscure pricing or charge users without clear consent will be rejected immediately.

## Date and Time Formatting

All dates and times displayed in your app must follow Stripe Dashboard conventions:

- Use the user’s locale for date formatting when available
- Display times in the user’s local timezone
- Use relative timestamps for recent events (e.g., “2 hours ago”)
- Use absolute timestamps for older events with full date and time
- Follow ISO 8601 for any API-facing date fields

```typescript
// Good: Use Stripe's date formatting utilities
import { formatDate, formatRelativeTime } from '@stripe/ui-extension-sdk/utils';

const formattedDate = formatDate(timestamp); // Locale-aware
const relativeTime = formatRelativeTime(timestamp); // "2 hours ago"
```

## App Settings

If your app requires configuration:

- Provide a dedicated **Settings view** accessible from the app’s viewport
- Pre-populate sensible defaults where possible
- Validate all user inputs with clear error messages
- Allow users to update settings without reinstalling the app
- Persist settings across sessions using the Stripe Secret Store API

## Sandbox Support

Your app must work correctly in Stripe’s sandbox (test) mode:

- **Test mode compatibility**: All features must function in test mode
- **Test data**: Use realistic test data that demonstrates app functionality
- **No live data in sandbox**: Never expose production data in test mode
- **Graceful handling**: If a feature is unavailable in sandbox, display a clear message explaining why
- Set `sandbox_install_compatible: true` in your manifest

```json
{
  "sandbox_install_compatible": true
}
```

## App Functionality

### Reliability

- The app must not crash or freeze during normal use
- All advertised features must work as described
- Network errors must be handled gracefully with retry options
- The app must remain responsive during background operations

### Completeness

- No placeholder content, “coming soon” features, or broken links
- All UI elements must be functional, no dead buttons or inactive controls
- Help text and documentation links must resolve to valid pages
- Uninstallation must cleanly remove all app data and webhooks

### Performance

- UI must render within 3 seconds on a standard connection
- Background sync operations must not block the UI
- Large data sets must use pagination or lazy loading
- Minimize API calls to avoid rate limiting

## Developer Standards

### API Usage

- Use the latest stable version of the Stripe API
- Follow Stripe API best practices for pagination, error handling, and idempotency
- Do not exceed rate limits, implement exponential backoff for retries
- Use webhooks for event-driven updates instead of polling

### Code Quality

- No console errors or warnings in production builds
- Remove all debug logging before submission
- Handle all edge cases (empty states, missing data, network failures)
- Follow Stripe’s component library patterns for consistent UI

### Versioning

- Use semantic versioning (MAJOR.MINOR.PATCH)
- Document breaking changes in version updates
- Maintain backward compatibility where possible

## UX Quality

### Advertising

- **No ads**: Your app must not display advertisements of any kind
- **No cross-promotion**: Do not promote other products or services within the app UI
- **Branded content**: Only display your own brand identity, not third-party brands (except integration partners like Brevo)

### Language and Content

- **Consistent language**: Use consistent terminology throughout the app
- **Professional tone**: Match the Stripe Dashboard’s professional, concise communication style
- **No jargon**: Avoid technical jargon that merchants may not understand
- **Spell check**: Ensure all text is free of spelling and grammatical errors
- **English only**: All user-facing text must be in English for marketplace apps

### Confirmation Dialogs

Require user confirmation for destructive or significant actions:

```typescript
// Good: Confirm before destructive actions
const handleDisconnect = async () => {
  const confirmed = await showConfirmation({
    title: 'Disconnect Brevo Integration',
    message: 'This will stop syncing customer data to Brevo. You can reconnect at any time.',
    confirmLabel: 'Disconnect',
    cancelLabel: 'Cancel',
    destructive: true,
  });

  if (confirmed) {
    await disconnectIntegration();
  }
};
```

Actions that require confirmation:

- Disconnecting integrations
- Deleting synced data
- Changing settings that affect data flow
- Resetting configuration to defaults

### Loading States

Always show loading indicators for asynchronous operations:

```typescript
// Good: Show loading state during data fetch
const CustomerSyncStatus = () => {
  const { data, isLoading, error } = useSyncStatus();

  if (isLoading) {
    return <Spinner label="Loading sync status..." />;
  }

  if (error) {
    return <Banner type="critical" title="Failed to load sync status">
      {error.message}
    </Banner>;
  }

  return <SyncStatusDisplay data={data} />;
};
```

Requirements:

- Show spinners or skeleton screens during data loading
- Disable buttons during form submission
- Display progress indicators for long-running operations
- Never show a blank screen while loading

### Error Messages

Provide clear, actionable error messages:

```typescript
// Bad: Generic error
"Something went wrong"

// Good: Specific and actionable
"Unable to sync customer data to Brevo. Please verify your Brevo API key
in Settings and try again."
```

Error message guidelines:

- Explain what happened in plain language
- Suggest a specific action the user can take to resolve the issue
- Provide a way to retry the failed operation
- Log detailed error information for debugging (not shown to users)
- Include error codes for support reference when applicable

## Security

### Secret Store API

Use Stripe’s Secret Store API for all sensitive data storage:

```typescript
import { createHttpClient, STRIPE_API_KEY } from '@stripe/ui-extension-sdk/http_client';

// Good: Store secrets using the Secret Store API
const storeBrevoApiKey = async (apiKey: string) => {
  const stripe = createHttpClient(STRIPE_API_KEY);
  await stripe.apps.secrets.create({
    name: 'brevo_api_key',
    payload: apiKey,
    scope: { type: 'account' },
  });
};

// Good: Retrieve secrets from the Secret Store
const getBrevoApiKey = async () => {
  const stripe = createHttpClient(STRIPE_API_KEY);
  const secret = await stripe.apps.secrets.find({
    name: 'brevo_api_key',
    scope: { type: 'account' },
  });
  return secret.payload;
};
```

**Never** store sensitive data in:

- Local storage or session storage
- Cookies
- URL parameters
- Hardcoded values in source code
- Plain text configuration files

### Cryptography

- **No custom cryptography**: Do not implement your own encryption algorithms
- Use Stripe’s built-in security primitives (Secret Store, signing secrets)
- Use HTTPS for all external API calls
- Validate all webhook signatures before processing

### Data Handling

- Only request permissions your app actually needs
- Do not store Stripe data outside of what is necessary for functionality
- Implement data retention policies aligned with your privacy policy
- Provide a mechanism for users to request data deletion

## Legal Compliance

### Privacy Policy

Your app must have a publicly accessible privacy policy that covers:

- What data your app collects from Stripe
- How the data is stored, processed, and shared
- Data retention and deletion policies
- User rights regarding their data
- Contact information for privacy inquiries
- Compliance with applicable regulations (GDPR, CCPA, etc.)

### Terms of Service

- Provide clear terms of service for your app
- Do not include terms that conflict with Stripe’s Terms of Service
- Clearly state any usage limitations or restrictions

### Regulatory Compliance

- Comply with all applicable data protection regulations
- Implement appropriate data processing agreements
- Support data portability and deletion requests
- Maintain audit logs for data access and processing

## Review Process Timeline

| Stage | Duration |
| --- | --- |
| Initial submission | 5-10 business days |
| Revision review | 3-7 business days |
| Final approval | 1-2 business days |
| Publication | Immediate after approval |

Tip

Address all review feedback in a single revision to avoid multiple review cycles. The Stripe team provides specific, actionable feedback for each issue found.

## Common Rejection Reasons

1. **Missing error handling**, App crashes on network errors or unexpected data
2. **Insufficient loading states**, Blank screens during data fetching
3. **Unclear pricing**, Pricing not fully disclosed in the listing
4. **Excessive permissions**, Requesting permissions not needed by the app
5. **Broken sandbox mode**, App doesn’t function in test mode
6. **Security issues**, Storing secrets outside the Secret Store API
7. **Missing privacy policy**, No accessible privacy policy URL
8. **Incomplete functionality**, “Coming soon” features or placeholder content
