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.
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:
- Custom Base URL: Route SDK traffic through custom domains, proxies, or CDNs
- 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/)
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:
- SDK connects to server via HTTPS
- Server presents its certificate chain
- SDK computes SHA-256 hash of each certificate's public key
- SDK compares hashes against your configured pins
- 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.
Method 1: Using OpenSSL (Recommended)
# 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:
- Android: Throw
IOExceptionwith message "Certificate pinning failed" - iOS: Cancel the network request and trigger error callback
- 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
- Choose your custom domain and set up DNS/proxy infrastructure
- Test connectivity to ensure your custom URL serves StorifyMe API correctly
- Update SDK initialization with
customBaseUrlparameter - Test thoroughly in staging environment
- Deploy gradually using phased rollout
Adding Certificate Pinning to Existing App
- Obtain current certificate pins using OpenSSL method above
- Get backup certificate pins for your next scheduled certificate
- Update SDK initialization with
certificatePinsparameter - Test in staging with your staging server pins
- Monitor closely after production deployment
- Plan pin updates 60-90 days before certificate expiration
Troubleshooting
| Issue | Solution |
|---|---|
| "Certificate pinning failed" | Verify pins are correct using OpenSSL. Check if certificate was rotated. |
| "Invalid request" with custom URL | Ensure custom URL supports StorifyMe API endpoints and authentication. |
| Stories not loading | Check network connectivity to custom base URL. Verify DNS resolution. |
| Pinning works in dev but not prod | Ensure production pins match production certificate, not staging. |
| App crashes on init | Check pin format is exactly sha256/[base64] with no extra spaces. |
Security Considerations
- 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:
- Email: support@storifyme.com
- Documentation: https://docs.storifyme.com
- GitHub Issues: Report SDK-specific issues on GitHub
Last updated: January 2026