The Rust server library for the App Store Server API, App Store Server Notifications, the Retention Messaging API, and Advanced Commerce API.
- Rust 1.88.0 or later
Specify app-store-server-library in your project's Cargo.toml file, under the [dependencies] section:
[dependencies]
app-store-server-library = { version = "6.0.0", features = ["aws_lc", "receipt-utility", "api-client-reqwest"] }There are no default features.
aws_lc- aws-lc-rs backend. FIPS-friendly, needs a C build toolchain.ring- ring backend.rust_crypto- Pure-Rust backend (p256,p384,rsa,sha2). No C toolchain required.
The backend is also forwarded to the x509-validator dependency used for certificate chain verification, so the whole crate ends up on a single crypto stack.
api-client- Enables the App Store Server / Advanced Commerce / Retention Messaging API clients. Bring your own HTTP client by implementing theTransporttrait.api-client-reqwest-api-clientplus thereqwesttransport, using rustls for TLS.api-client-reqwest-native-tls- Same, but using the platform's native-tls.
The reqwest transport requires a TLS backend; enabling
reqwestwithout one is a compile error rather than a runtime connection failure.
receipt-utility- Enables receipt processing and transaction ID extraction
Check crates.io for the latest version number.
| API | Version |
|---|---|
| App Store Server API | 1.21 |
| Retention Messaging API | 1.5 |
| Advanced Commerce API | 1.2 |
To use the App Store Server API or create promotional offer signatures, a signing key downloaded from App Store Connect is required. To obtain this key, you must have the Admin role. Go to Users and Access > Integrations > In-App Purchase. Here you can create and manage keys, as well as find your Issuer ID. When using a key, you'll need the Key ID and the Issuer ID as well.
Download and store the root certificates found in the Apple Root Certificates section of the Apple PKI site. Provide these certificates as an array to a SignedDataVerifier to allow verifying the signed data comes from Apple.
The library ships three clients, matching the three APIs. All of them take a signing key as raw PEM bytes, return a Result from new, and are generic over the Transport trait — ReqwestHttpTransport is provided when the api-client-reqwest feature is on, but any HTTP client works.
// NOTE: .unwrap() used for example purposes only
use app_store_server_library::app_store_server_api_client::AppStoreServerApiClient;
use app_store_server_library::api_client::reqwest_transport::reqwest_http_transport::ReqwestHttpTransport;
use app_store_server_library::models::app_store_environment::Environment;
#[tokio::main]
async fn main() {
let issuer_id = "99b16628-15e4-4668-972b-eeff55eeff55";
let key_id = "ABCDEFGHIJ";
let bundle_id = "com.example";
let signing_key = std::fs::read("/path/to/key/SubscriptionKey_ABCDEFGHIJ.p8").unwrap(); // Adjust the path accordingly
let environment = Environment::Sandbox;
let transport = ReqwestHttpTransport::new(); // You can use any http client, but you must implement `Transport` trait for it.
let client = AppStoreServerApiClient::new(signing_key, key_id, issuer_id, bundle_id, environment, transport).unwrap();
match client.request_test_notification().await {
Ok(response) => {
println!("{}", response.test_notification_token);
}
Err(err) => {
println!("{:?}", err);
}
}
}The Retention Messaging API methods (message list, image upload, default configuration, performance tests, realtime URL) live on this same AppStoreServerApiClient, matching the Swift library's layout.
// NOTE: .unwrap() used for example purposes only
use app_store_server_library::advanced_commerce_api_client::AdvancedCommerceApiClient;
use app_store_server_library::api_client::reqwest_transport::reqwest_http_transport::ReqwestHttpTransport;
use app_store_server_library::models::app_store_environment::Environment;
#[tokio::main]
async fn main() {
let issuer_id = "99b16628-15e4-4668-972b-eeff55eeff55";
let key_id = "ABCDEFGHIJ";
let bundle_id = "com.example";
let signing_key = std::fs::read("/path/to/key/SubscriptionKey_ABCDEFGHIJ.p8").unwrap(); // Adjust the path accordingly
let environment = Environment::Sandbox;
let transport = ReqwestHttpTransport::new(); // You can use any http client, but you must implement `Transport` trait for it.
let client = AdvancedCommerceApiClient::new(signing_key, key_id, issuer_id, bundle_id, environment, transport).unwrap();
let transaction_id = "txId";
let subscription_cancel_request = AdvancedCommerceSubscriptionCancelRequest { /* .. */ };
match client.cancel_subscription(transaction_id, &subscription_cancel_request).await {
Ok(response) => {
println!("{}", response.signed_renewal_info);
println!("{}", response.signed_transaction_info);
}
Err(err) => {
println!("{:?}", err);
}
}
}// NOTE: .unwrap() used for example purposes only
let root_cert = "apple-root-cert-in-base-base64-format"; // https://www.apple.com/certificateauthority/AppleRootCA-G3.cer
let root_cert_der = root_cert.as_der_bytes().unwrap(); // Use `base64` crate to decode base64 string into bytes
let verifier = SignedDataVerifier::new(
vec![root_cert_der], // Vector of root certificates
Environment::Sandbox, // Environment
"app.superapp.apple".to_string(), // Bundle id
Some(12345678), // App id — required in Production
false, // enable_online_checks
).unwrap();
let payload = "signed-payload";
let decoded_payload = verifier.verify_and_decode_notification(payload).unwrap();Certificate chain verification is delegated to the x509-validator crate, which runs on the same crypto backend you selected above.
let receipt = "MI..";
let transaction_id = extract_transaction_id_from_app_receipt(receipt);Note: To extract transaction id from app/tx receipt,
receipt-utilityfeature must be enabled.
// NOTE: .unwrap() used for example purposes only
use app_store_server_library::promotional_offer_signature_creator::PromotionalOfferSignatureCreator;
let private_key = include_str!("../assets/SubscriptionKey_L256SYR32L.p8");
let creator = PromotionalOfferSignatureCreator::new(private_key, "L256SYR32L".to_string(), "com.test.app".to_string()).unwrap();
let nonce = uuid::Uuid::new_v4();
let timestamp = chrono::Utc::now().timestamp_millis();
let signature: String = creator.create_signature(
"com.test.product",
"com.test.offer",
uuid::Uuid::new_v4().to_string().as_str(), // app account token
&nonce,
timestamp
).unwrap();// NOTE: .unwrap() used for example purposes only
use app_store_server_library::jws_signature_creator::PromotionalOfferV2SignatureCreator;
let private_key = include_str!("../assets/SubscriptionKey_L256SYR32L.p8");
let creator = PromotionalOfferV2SignatureCreator::new(
private_key,
"L256SYR32L".to_string(), // Key ID
"issuer_id".to_string(), // Issuer ID
"com.test.app".to_string() // Bundle ID
).unwrap();
let signature: String = creator.create_signature(
"com.test.product", // Product ID
"com.test.offer", // Offer identifier
Some("transaction_id".to_string()) // Optional transaction ID
).unwrap();// NOTE: .unwrap() used for example purposes only
use app_store_server_library::jws_signature_creator::IntroductoryOfferEligibilitySignatureCreator;
let private_key = include_str!("../assets/SubscriptionKey_L256SYR32L.p8");
let creator = IntroductoryOfferEligibilitySignatureCreator::new(
private_key,
"L256SYR32L".to_string(), // Key ID
"issuer_id".to_string(), // Issuer ID
"com.test.app".to_string() // Bundle ID
).unwrap();
let signature: String = creator.create_signature(
"com.test.product", // Product ID
true, // Allow introductory offer
"transaction_id" // Transaction ID
).unwrap();- Receive request object from the client.
- Or create request from the server side.
Supported request objects (any type implementing AdvancedCommerceInAppRequest): AdvancedCommerceOneTimeChargeCreateRequest, AdvancedCommerceSubscriptionCreateRequest, AdvancedCommerceSubscriptionModifyInAppRequest or AdvancedCommerceSubscriptionReactivateInAppRequest.
// NOTE: .unwrap() used for example purposes only
use app_store_server_library::jws_signature_creator::AdvancedCommerceInAppSignatureCreator;
let request_object = ... // Receive from client side or create on server side
let private_key = include_str!("../assets/SubscriptionKey_L256SYR32L.p8");
let creator = AdvancedCommerceInAppSignatureCreator::new(
private_key,
"L256SYR32L".to_string(), // Key ID
"issuer_id".to_string(), // Issuer ID
"com.test.app".to_string() // Bundle ID
).unwrap();
let signature: String = creator.create_signature(
advanced_commerce_in_app_request: &request_object
).unwrap();- Upgrading from 4.x? See the CHANGELOG for breaking changes and a migration table.
- The full documentation is available at docs.rs
- App Store Server API Documentation
- App Store Server Notifications Documentation
- Retention Messaging API Documentation
- Advanced Commerce API Documentation
- WWDC Video
- Apple App Store Server Python Library
- Apple App Store Server Java Library
- Apple App Store Server Node Library
- Apple App Store Server Swift Library
Two crates, in bench/:
measure— Regression benchmarks.compare— Compare backends and parsers (results: backends, rust-vs-others).
app-store-server-library is distributed under the following two licenses:
- Apache License version 2.0.
- MIT license.
These are included as LICENSE-APACHE and LICENSE-MIT respectively.
You may use this software under the terms of any of these licenses, at your option.