iOS Receipt Validation (StoreKit 2)
API for validating the signed transaction (Signed Transaction, JWS) of StoreKit 2.
This API uses the service-api.playnanoo.com domain.
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
| Parameter | Type | Required | Description |
|---|---|---|---|
| jws_transaction | string | Required | Signed transaction (JWS) |
| product_id | string | Required | Payment product ID |
| duplicate_allow | string | Optional | Allow duplicate receipt validation (Y/N) |
Unlike the legacy v20221001, currency and price are not passed. The server extracts the currency and amount directly from the JWS payload.
Response Data
| Field | Type | Description |
|---|---|---|
| UserID | string | User ID |
| PackageName | string | Package name |
| OrderID | string | Order ID (transactionId) |
| ProductID | string | Product ID |
| Quantity | string | Quantity |
| Currency | string | Currency code |
| Price | string | Price |
| PurchaseState | string | Validation 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();
}
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.
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.
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.