Skip to main content

Custom Base URL and Certificate Pinning

Learn how to configure custom base URLs and implement certificate pinning for enhanced security and flexibility across all StorifyMe mobile SDKs.

Enterprise Feature

Custom base URL and certificate pinning are available on all SDK tiers and provide enterprise-grade security and flexibility.

Overview

StorifyMe SDKs support two powerful features for enterprise deployments:

  1. Custom Base URL: Route SDK traffic through custom domains, proxies, or CDNs
  2. Certificate Pinning: Secure network communications by validating server certificates against known public keys

Both features are optional and independent - use them separately or together based on your requirements.

When to Use These Features

Custom Base URL

Use a custom base URL when you need to:

  • Custom Domain: Serve content from your branded domain (e.g., stories.yourapp.com)
  • Corporate Proxy: Route traffic through corporate infrastructure for security/compliance
  • Custom CDN: Leverage your existing CDN infrastructure
  • Path-Based Routing: Serve stories from a subpath (e.g., yourapp.com/stories/)
  • Testing: Point to staging or development environments

Certificate Pinning

Use certificate pinning when you need to:

  • Prevent MITM Attacks: Protect against man-in-the-middle attacks even with compromised CAs
  • Regulatory Compliance: Meet industry security requirements (banking, healthcare, etc.)
  • Zero-Trust Security: Implement defense-in-depth security strategies
  • Enhanced Verification: Add an extra layer beyond standard TLS/SSL validation

Custom Base URL

How It Works

When you provide a customBaseUrl, the SDK uses it instead of the default environment URL for all API requests:

  • Default: https://storifyme.xyz/ (EU), https://us.storifyme.xyz/ (US)
  • Custom: Your specified URL (e.g., https://stories.yourapp.com/)
Important

Even with a custom base URL, you must still specify the environment (EU/US/DEV) because it's used for crash analytics reporting.

Implementation by Platform

Android (Kotlin)

import com.storify.android_sdk.StorifyMe
import com.storify.android_sdk.StorifyMeEnv

class MyApplication : Application() {
override fun onCreate() {
super.onCreate()

StorifyMe.init(
context = this,
apiKey = "your-api-key",
accountId = "your-account-id",
environment = StorifyMeEnv.EU, // Still required for analytics
customBaseUrl = "https://stories.yourapp.com/"
)
}
}

iOS (Swift)

import StorifyMe

func application(_ application: UIApplication,
didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool {

StorifyMeInstance.shared.initialize(
accountId: "your-account-id",
apiKey: "your-api-key",
env: .EU, // Still required for analytics
customBaseUrl: "https://stories.yourapp.com/",
certificatePins: nil
)

return true
}

React Native (TypeScript)

import { StorifyMe, StorifyMeEnv } from 'react-native-storifyme';

await StorifyMe.initialize(
"your-account-id",
"your-api-key",
StorifyMeEnv.EU, // Still required for analytics
{
customBaseUrl: "https://stories.yourapp.com/"
}
);

Flutter (Dart)

import 'package:storifyme_flutter/storifyme_flutter.dart';

await _storifyMePlugin.initPlugin({
Params.API_KEY_ID: 'your-api-key',
Params.ACCOUNT_ID_KEY: 'your-account-id',
Params.ENVIRONMENT_KEY: 'EU', // Still required for analytics
Params.CUSTOM_BASE_URL: 'https://stories.yourapp.com/'
});

URL Format Guidelines

Your custom base URL should:

  • ✅ Use HTTPS protocol (required for security)
  • ✅ Include trailing slash for cleaner path concatenation
  • ✅ Be a valid, reachable domain
  • ✅ Support the same API endpoints as StorifyMe servers

Examples:

  • https://stories.yourapp.com/ (subdomain)
  • https://api.yourapp.com/storifyme/ (subpath)
  • https://yourapp.com/stories/ (path-based)
  • https://proxy.company.com/ (corporate proxy)

Certificate Pinning

How It Works

Certificate pinning validates that the server's SSL/TLS certificate matches one of your pre-configured public key hashes. This prevents man-in-the-middle attacks even if a Certificate Authority is compromised.

Pinning Process:

  1. SDK connects to server via HTTPS
  2. Server presents its certificate chain
  3. SDK computes SHA-256 hash of each certificate's public key
  4. SDK compares hashes against your configured pins
  5. Connection succeeds only if at least one pin matches

Obtaining Certificate Pins

You need the SHA-256 hash of your server certificate's public key in base64 format.

# Get certificate from server
echo | openssl s_client -servername storifyme.com -connect storifyme.com:443 2>/dev/null | \
openssl x509 -pubkey -noout | \
openssl pkey -pubin -outform der | \
openssl dgst -sha256 -binary | \
openssl enc -base64

# Output: YLh1dUR9y6Kja30RrAn7JKnbQG/uEtLMkBgFF2Fuihg=

Method 2: Using Certificate File

If you have a .crt or .pem certificate file:

openssl x509 -in certificate.crt -pubkey -noout | \
openssl pkey -pubin -outform der | \
openssl dgst -sha256 -binary | \
openssl enc -base64

Implementation by Platform

Android (Kotlin)

StorifyMe.init(
context = this,
apiKey = "your-api-key",
accountId = "your-account-id",
environment = StorifyMeEnv.EU,
customBaseUrl = "https://stories.yourapp.com/", // Optional
certificatePins = listOf(
"sha256/YLh1dUR9y6Kja30RrAn7JKnbQG/uEtLMkBgFF2Fuihg=", // Primary cert
"sha256/AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA=" // Backup cert
)
)

iOS (Swift)

StorifyMeInstance.shared.initialize(
accountId: "your-account-id",
apiKey: "your-api-key",
env: .EU,
customBaseUrl: nil, // Use default URL
certificatePins: [
"sha256/YLh1dUR9y6Kja30RrAn7JKnbQG/uEtLMkBgFF2Fuihg=", // Primary cert
"sha256/AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA=" // Backup cert
]
)

React Native (TypeScript)

await StorifyMe.initialize(
"your-account-id",
"your-api-key",
StorifyMeEnv.EU,
{
certificatePins: [
"sha256/YLh1dUR9y6Kja30RrAn7JKnbQG/uEtLMkBgFF2Fuihg=", // Primary cert
"sha256/AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA=" // Backup cert
]
}
);

Flutter (Dart)

await _storifyMePlugin.initPlugin({
Params.API_KEY_ID: 'your-api-key',
Params.ACCOUNT_ID_KEY: 'your-account-id',
Params.ENVIRONMENT_KEY: 'EU',
Params.CERTIFICATE_PINS: [
'sha256/YLh1dUR9y6Kja30RrAn7JKnbQG/uEtLMkBgFF2Fuihg=', // Primary cert
'sha256/AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA=' // Backup cert
]
});

Pin Format

Certificate pins must follow this exact format:

sha256/[base64-encoded-hash]

Example: sha256/YLh1dUR9y6Kja30RrAn7JKnbQG/uEtLMkBgFF2Fuihg=

  • Prefix: sha256/ (lowercase, required)
  • Hash: Base64-encoded SHA-256 hash of the certificate's public key
  • Length: 44 characters after the prefix (base64 of 32-byte SHA-256 hash)

Best Practices

1. Use Multiple Pins (Pin Backup Certificates)

Always configure at least 2 pins to prevent app outages during certificate rotation:

certificatePins = listOf(
"sha256/CurrentCertificatePin=", // Active certificate
"sha256/BackupCertificatePin=" // Backup for rotation
)

2. Pin Intermediate or Root Certificates

Consider pinning your CA's intermediate or root certificate instead of (or in addition to) your leaf certificate:

  • Leaf Certificate: Changes frequently (every 90 days for Let's Encrypt), requires app updates
  • Intermediate/Root: Changes rarely, more flexible
For Let's Encrypt Users

If you're using Let's Encrypt, always pin both your leaf certificate AND the intermediate certificate to avoid app outages during automatic certificate rotation.

Step 1: Get your leaf certificate pin

# Replace with your actual domain
echo | openssl s_client -servername stories.yourapp.com -connect stories.yourapp.com:443 2>/dev/null | \
openssl x509 -pubkey -noout | \
openssl pkey -pubin -outform der | \
openssl dgst -sha256 -binary | \
openssl enc -base64

Step 2: Get Let's Encrypt intermediate certificate pin

Let's Encrypt uses the E8 intermediate certificate (valid until September 30, 2027):

curl -s https://letsencrypt.org/certs/2024/e8.pem | \
openssl x509 -pubkey -noout | \
openssl pkey -pubin -outform der | \
openssl dgst -sha256 -binary | \
openssl enc -base64

# Output: iFvwVyJSxnQdyaUvUERIf+8qk7gRze3612JMwoO3zdU=

Step 3: Use both pins

StorifyMe.init(
context = this,
apiKey = "your-api-key",
accountId = "your-account-id",
environment = StorifyMeEnv.EU,
customBaseUrl = "https://stories.yourapp.com",
certificatePins = listOf(
"sha256/[YOUR-LEAF-CERT-HASH]=", // From Step 1
"sha256/iFvwVyJSxnQdyaUvUERIf+8qk7gRze3612JMwoO3zdU=" // Let's Encrypt E8
)
)

Why this works:

  • Your leaf certificate rotates every ~90 days automatically
  • The E8 intermediate certificate stays the same until 2027
  • The SDK accepts a connection if ANY certificate in the chain matches ANY pin
  • Even after rotation, the E8 pin continues to match → No app update needed until 2027! ✅

3. Test in Staging First

Always test certificate pinning in a staging environment before production deployment.

4. Monitor Certificate Expiration

Set up monitoring to alert you 60-90 days before certificate expiration to plan pin updates.

5. Have a Rollback Plan

Ensure you can release an app update quickly if pinning causes issues in production.

Combined Usage Examples

Enterprise Deployment with Full Security

// Android - Custom domain with certificate pinning
StorifyMe.init(
context = applicationContext,
apiKey = "your-api-key",
accountId = "your-account-id",
environment = StorifyMeEnv.EU,
customBaseUrl = "https://stories.yourcompany.com/",
certificatePins = listOf(
"sha256/PrimaryCertPin=",
"sha256/BackupCertPin="
)
)

Corporate Proxy with Pinning

// iOS - Route through corporate proxy with validation
StorifyMeInstance.shared.initialize(
accountId: "your-account-id",
apiKey: "your-api-key",
env: .EU,
customBaseUrl: "https://proxy.corporate.com/storifyme/",
certificatePins: [
"sha256/ProxyCertPin=",
"sha256/ProxyBackupPin="
]
)

Staging Environment Testing

// React Native - Point to staging environment
await StorifyMe.initialize(
"staging-account-id",
"staging-api-key",
StorifyMeEnv.DEV,
{
customBaseUrl: "https://staging-stories.yourapp.com/"
// No pinning for staging to simplify testing
}
);

Error Handling

Certificate Pinning Failures

If certificate validation fails, the SDK will:

  1. Android: Throw IOException with message "Certificate pinning failed"
  2. iOS: Cancel the network request and trigger error callback
  3. React Native/Flutter: Propagate error through native bridges

Common causes:

  • Certificate was rotated but app wasn't updated with new pins
  • Incorrect pin format or hash value
  • Network intercepting proxy (corporate firewall) with different certificate
  • Server misconfiguration

Debugging Tips

Android

Enable debug logging to see certificate validation details:

// The SDK logs pinning success/failure automatically
// Check LogCat for: "Certificate pinning validation successful"

iOS

Check console logs for pinning-related messages during connection attempts.

Testing Certificate Pinning

Test with Invalid Pin:

certificatePins = listOf("sha256/InvalidPinForTesting=")
// Should fail and log error - confirms pinning is working

Test with Valid Pin:

certificatePins = listOf("sha256/YourActualCertificatePin=")
// Should succeed - confirms pin is correct

Frequently Asked Questions

Q: Can I use custom base URL without certificate pinning?

Yes! Both features are independent and optional. Use them separately or together.

Q: Will custom base URL affect analytics?

No. Analytics and crash reporting still use the environment-specific URLs (EU/US/DEV) for proper data routing.

Q: How often should I rotate pins?

Follow your certificate rotation schedule. Typically certificates are valid for 1-2 years. Update app with new pins before rotating certificates.

Q: What happens if all pins become invalid?

The SDK will fail to connect. You'll need to release an app update with valid pins. This is why having backup pins is critical.

Q: Can I pin the default StorifyMe servers?

Yes! You can use certificate pinning without a custom base URL to secure connections to standard StorifyMe servers.

Q: Does this work with self-signed certificates?

Yes, but only for testing. Production should always use certificates from trusted CAs. You can pin self-signed certificates for staging/development environments.

Q: What's the performance impact?

Certificate pinning adds minimal overhead (< 50ms) during the initial TLS handshake. Subsequent requests use the established connection.

Migration Guide

Adding Custom Base URL to Existing App

  1. Choose your custom domain and set up DNS/proxy infrastructure
  2. Test connectivity to ensure your custom URL serves StorifyMe API correctly
  3. Update SDK initialization with customBaseUrl parameter
  4. Test thoroughly in staging environment
  5. Deploy gradually using phased rollout

Adding Certificate Pinning to Existing App

  1. Obtain current certificate pins using OpenSSL method above
  2. Get backup certificate pins for your next scheduled certificate
  3. Update SDK initialization with certificatePins parameter
  4. Test in staging with your staging server pins
  5. Monitor closely after production deployment
  6. Plan pin updates 60-90 days before certificate expiration

Troubleshooting

IssueSolution
"Certificate pinning failed"Verify pins are correct using OpenSSL. Check if certificate was rotated.
"Invalid request" with custom URLEnsure custom URL supports StorifyMe API endpoints and authentication.
Stories not loadingCheck network connectivity to custom base URL. Verify DNS resolution.
Pinning works in dev but not prodEnsure production pins match production certificate, not staging.
App crashes on initCheck pin format is exactly sha256/[base64] with no extra spaces.

Security Considerations

Security Best Practices
  • Never commit certificates or private keys to source control
  • Store pins securely in your app configuration
  • Use ProGuard/R8 (Android) to obfuscate pin strings
  • Monitor certificate expiration to prevent outages
  • Test pin updates in staging before production
  • Have a backup plan for emergency certificate rotation

Additional Resources

Support

Need help implementing custom base URL or certificate pinning? Contact our support team:


Last updated: January 2026