Skip to main content

iOS Receipt Validation (StoreKit 2)

API for validating the signed transaction (Signed Transaction, JWS) of StoreKit 2.

URL Verification

This API uses the service-api.playnanoo.com domain.

StoreKit 2 (JWS) Method

This API validates the StoreKit 2 (iOS 15.0 or later) signed transaction (JWS). If you use the Base64 receipt of StoreKit 1, refer to iOS Receipt Validation. Since the Unreal SDK does not have a dedicated helper, call it via raw HTTP as shown below.

API Information

  • URL: https://service-api.playnanoo.com/iap/v20260401/ios
  • Method: PUT
  • Authentication Required: Yes

Request Parameters

ParameterTypeRequiredDescription
jws_transactionstringRequiredSigned transaction (JWS)
product_idstringRequiredPayment product ID
duplicate_allowstringOptionalAllow duplicate receipt validation (Y/N)
No currency / price Parameters

Unlike the legacy v20221001, currency and price are not passed. The server extracts the currency and amount directly from the JWS payload.

Response Data

FieldTypeDescription
UserIDstringUser ID
PackageNamestringPackage name
OrderIDstringOrder ID (transactionId)
ProductIDstringProduct ID
QuantitystringQuantity
CurrencystringCurrency code
PricestringPrice
PurchaseStatestringValidation result (purchase / wait / cancel)

Code Example

void UMyGame::ValidateIOSIAPJWS(const FString& JwsTransaction, const FString& ProductId, bool bDuplicateAllow)
{
// 플레이어 정보가 포함된 요청 바디 생성
TSharedPtr<FJsonObject> Body = FPlayNANOOHelper::CreateRequestBody();
Body->SetStringField(TEXT("jws_transaction"), JwsTransaction);
Body->SetStringField(TEXT("product_id"), ProductId);
Body->SetStringField(TEXT("duplicate_allow"), bDuplicateAllow ? TEXT("Y") : TEXT("N"));

// JSON 문자열 변환
FString JsonBody = FPlayNANOOHelper::ToJsonString(Body);

// HTTP 요청
TSharedRef<IHttpRequest> Request = FHttpModule::Get().CreateRequest();
Request->SetURL(TEXT("https://service-api.playnanoo.com/iap/v20260401/ios"));
Request->SetVerb(TEXT("PUT"));
FPlayNANOOHelper::SetCommonHeaders(Request, true); // 인증 토큰 포함
Request->SetContentAsString(JsonBody);

Request->OnProcessRequestComplete().BindLambda(
[](FHttpRequestPtr Req, FHttpResponsePtr Res, bool bSuccess)
{
if (bSuccess && Res.IsValid())
{
TSharedPtr<FJsonObject> JsonObject;
TSharedRef<TJsonReader<>> Reader = TJsonReaderFactory<>::Create(Res->GetContentAsString());

if (FJsonSerializer::Deserialize(Reader, JsonObject))
{
FString UserID = JsonObject->GetStringField(TEXT("UserID"));
FString OrderID = JsonObject->GetStringField(TEXT("OrderID"));
FString ProductID = JsonObject->GetStringField(TEXT("ProductID"));
FString ValidatedPrice = JsonObject->GetStringField(TEXT("Price"));
FString ValidatedCurrency = JsonObject->GetStringField(TEXT("Currency"));
FString PurchaseState = JsonObject->GetStringField(TEXT("PurchaseState"));

UE_LOG(LogTemp, Log, TEXT("IAP 검증 성공 - 주문ID: %s, 상품ID: %s, 가격: %s %s, 상태: %s"),
*OrderID, *ProductID, *ValidatedPrice, *ValidatedCurrency, *PurchaseState);
}
}
});

Request->ProcessRequest();
}
Obtaining the JWS

jws_transaction is the signed transaction (Signed Transaction) string of StoreKit 2. How to obtain it depends on the client's StoreKit 2 integration. StoreKit 2 requires iOS 15.0 or later.

Local Certificate Chain Validation

The server validates the x5c certificate chain embedded in the JWS against the Apple Root CA G3 (ES256), and no additional call to Apple servers or key file (.p8) is required. Sandbox / Xcode payments are stored in test mode and are not counted in revenue.

Refund Detection (Webhook)

If you register App Store Server Notifications (Version 2), refund (REFUND) events are received automatically. The registration URL is https://service-api.playnanoo.com/webhook/apple/{gameId}. For details, refer to API & Platform Settings > iOS in the console.