App Store Server Library for Elixir

Hex.pm Docs License: MIT

Elixir client for Apple's App Store Server API, Server Notifications, and Retention Messaging.

Installation

def deps do
  [{:app_store_server_library, "~> 2.0"}]
end

Setup

From App Store Connect → Users and Access → Integrations → In-App Purchase:

See Creating API keys to authorize API requests for thorough instructions.

Making API Calls

alias AppStoreServerLibrary.Client
alias AppStoreServerLibrary.Models.{ConsumptionRequest, TransactionHistoryRequest}

{:ok, client} = Client.new(
  signing_key: File.read!("AuthKey_XXXXXXXXXX.p8"),
  key_id: "YOUR_KEY_ID",
  issuer_id: "YOUR_ISSUER_ID",
  bundle_id: "com.example.app",
  environment: :sandbox  # or :production
)

# Transaction history
{:ok, response} = Client.get_transaction_history(client, "original_txn_id", nil, %TransactionHistoryRequest{})

# Subscription status
{:ok, statuses} = Client.get_all_subscription_statuses(client, "txn_id")

# Refund history
{:ok, refunds} = Client.get_refund_history(client, "txn_id")

# Send consumption data (V2 API)
Client.send_consumption_information(client, "txn_id", %ConsumptionRequest{
  customer_consented: true,
  delivery_status: :delivered,
  sample_content_provided: false
})

Bang variants raise on error: Client.get_transaction_info!(client, "txn_id")

Verifying Signed Data

Verify transactions and notifications from Apple. You'll need Apple's root certificates in DER format.

alias AppStoreServerLibrary.Verifier

{:ok, verifier} = Verifier.new(
  root_certificates: [File.read!("AppleRootCA-G3.cer")],
  enable_online_checks: true,  # OCSP verification
  environment: :sandbox,
  bundle_id: "com.example.app"
  # app_apple_id: 123456789  # required for :production
)

# Verify a signed transaction
case Verifier.verify_and_decode_signed_transaction(verifier, signed_transaction) do
  {:ok, transaction} ->
    transaction.transaction_id

  {:error, {:retryable_verification_failure, _}} ->
    # OCSP network hiccup - retry

  {:error, {status, message}} ->
    Logger.error("Verification failed: #{status}")
end

# Verify App Store Server Notification (v2)
{:ok, notification} = Verifier.verify_and_decode_notification(verifier, payload)
notification.notification_type  # e.g. :subscribed, :did_renew, :refund

Handling Webhooks

Typical notification endpoint:

def handle_notification(conn, %{"signedPayload" => signed_payload}) do
  case Verifier.verify_and_decode_notification(verifier(), signed_payload) do
    {:ok, notification} ->
      process_notification(notification)
      send_resp(conn, 200, "")

    {:error, _} ->
      send_resp(conn, 400, "")
  end
end

defp process_notification(%{notification_type: :did_renew} = n) do
  # Handle renewal
end

defp process_notification(%{notification_type: :refund} = n) do
  # Handle refund
end

Creating Promotional Offer Signatures

For StoreKit 2 promotional offers:

alias AppStoreServerLibrary.Signature.PromotionalOfferV2SignatureCreator

creator = PromotionalOfferV2SignatureCreator.new(
  signing_key: signing_key,
  key_id: "YOUR_KEY_ID",
  issuer_id: "YOUR_ISSUER_ID",
  bundle_id: "com.example.app"
)

{:ok, signature} = PromotionalOfferV2SignatureCreator.create_signature(
  creator,
  "product_id",
  "offer_id",
  "original_transaction_id"  # optional
)

Legacy V1 signatures and introductory offer eligibility signatures also supported—see the docs.

Extracting Transaction IDs from Receipts

For migrating from verifyReceipt to App Store Server API:

alias AppStoreServerLibrary.Utility.ReceiptUtility

{:ok, txn_id} = ReceiptUtility.extract_transaction_id_from_app_receipt(receipt_base64)

Error Handling

alias AppStoreServerLibrary.Client
alias AppStoreServerLibrary.API.APIException

case Client.get_transaction_info(client, "bad_id") do
  {:ok, txn} ->
    txn

  {:error, %APIException{api_error: :transaction_id_not_found}} ->
    nil

  {:error, %APIException{} = e} ->
    if APIException.retryable?(e) do
      # Safe to retry: rate limits, transient failures
      retry()
    else
      raise e
    end
end

Testing

Run the suite (OCSP live checks excluded by default):

mix test

To exercise real OCSP responders and Apple certificates, opt in to the live tag (requires outbound network):

mix test --include ocsp_live --include real_apple

Two settings let your own tests run the online-checks path without network access. Set them in config/test.exs only, never in production:

# Replaces the HTTP call. `send_ocsp_request/2` returns `{:ok, der_binary}` or an
# error tuple, and the response is still validated.
config :app_store_server_library, :ocsp_requester, MyOCSPRequester

# Replaces the whole revocation check. `check_ocsp_status/2` returns `:ok` or an
# error tuple. This disables revocation checking.
config :app_store_server_library, :ocsp_checker, MyOCSPChecker

Environments

Environment Notes
:production Requires app_apple_id
:sandbox Testing
:xcode / :local_testing Signature verification skipped

Warning: a verifier created with :xcode or :local_testing accepts any payload without checking its signature. Use those environments for local StoreKit testing only, never for an endpoint reachable from the network.

Batteries Included

Details in the docs.

Documentation

Full API reference, all models, telemetry events, caching configuration: hexdocs.pm/app_store_server_library