You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何使用PHP解密Apple Server-To-Server通知?

App Store Server Notifications v2 后续操作指引

我折腾了好几天,不确定是Apple的官方文档不完善还是缺少代码示例导致难度陡增,而且网上几乎找不到优质示例。目前已经完成以下步骤:

  • 在App信息中添加沙箱Webhook,通过ngrok隧道运行以接收信息;
  • 摸索后确定需要对响应内容进行Base64 URL解码,得到如下结构的对象:
Array
(
    [headers] => stdClass Object
        (
            [alg] => ES256
            [x5c] => Array
                (
                    [0] => ...,
                    [1] => ...,
                    [2] => ...,
                )

        )

    [payload] => stdClass Object
        (
            [notificationType] => DID_RENEW
            [notificationUUID] => ...uuid...
            [data] => stdClass Object
                (
                    [bundleId] => ...app.id...
                    [bundleVersion] => 1.5
                    [environment] => Sandbox
                    [signedTransactionInfo] => ... //long string of encrypted info
                )

            [version] => 2.0
            [signedDate] => 1670890743027
        )

    [signature] => ... // signature string
)

各关键字段说明:

  • headers->alg:使用的算法
  • headers->x5c:包含3个元素的数组,对应Apple的证书链(按顺序):
    1. 包含App Store签名JWS对应公钥的证书;
    2. Apple PKI站点的Worldwide Developer Relations中级证书;
    3. Apple根证书
  • payload:包含相关信息,其中payload->data->signedTransactionInfo为加密的交易信息;
  • signature:签名字符串,用于验证JWS的合法性

我理解需要使用响应中的密钥解密JWT,尝试了web-token PHP库,但不知如何继续,仅知晓使用ES256算法,以下是后续操作指引:


核心操作流程

1. 验证Webhook通知的合法性

Apple的Webhook通知是JWS格式,必须验证签名确保来源可信:

  • 从headers->x5c数组中取出第一个证书,将其转换为标准PEM格式:在证书内容前后添加-----BEGIN CERTIFICATE-----和-----END CERTIFICATE-----,并按每行64字符分割内容
  • 使用ES256算法,结合转换后的公钥证书,对原始Webhook响应的JWS字符串(不是你解码后的数组)进行签名验证

2. 解析加密的交易信息

signedTransactionInfo本身也是一个JWT,同样需要验证并解析:

  • 用ES256算法,结合Apple根证书(可从x5c数组的第三个元素转换为PEM格式)验证该JWT的签名
  • 验证通过后解码JWT,即可获取完整的交易详情(如交易ID、订阅到期时间、续期状态等)

使用web-token库的代码示例

use Jose\Component\Core\AlgorithmManager;
use Jose\Component\Signature\Algorithm\ES256;
use Jose\Component\Signature\JWSVerifier;
use Jose\Component\Signature\Serializer\CompactSerializer;
use Jose\Component\KeyManagement\JWKFactory;

// 初始化算法与验证器
$algorithmManager = AlgorithmManager::create([new ES256()]);
$jwsVerifier = new JWSVerifier($algorithmManager);
$serializer = new CompactSerializer();

// 获取原始Webhook的JWS字符串(假设请求体中字段为signedPayload)
$rawJws = $_POST['signedPayload'];

// 解析JWS并提取证书链
$jws = $serializer->unserialize($rawJws);
$protectedHeader = $jws->getSignature(0)->getProtectedHeader();
$x5cChain = $protectedHeader['x5c'];

// 将第一个证书转换为JWK格式
$publicCert = "-----BEGIN CERTIFICATE-----\n" . chunk_split($x5cChain[0], 64) . "-----END CERTIFICATE-----";
$publicKey = JWKFactory::createFromCertificate($publicCert);

// 验证通知签名
$isNotificationValid = $jwsVerifier->verifyWithKey($jws, $publicKey, 0);
if (!$isNotificationValid) {
    die("Invalid Apple notification signature");
}

// 解析通知Payload
$notificationPayload = json_decode($jws->getPayload(), true);

// 解析加密的交易信息
$signedTransactionJws = $notificationPayload['data']['signedTransactionInfo'];
$transactionJws = $serializer->unserialize($signedTransactionJws);

// 转换根证书为JWK
$rootCert = "-----BEGIN CERTIFICATE-----\n" . chunk_split($x5cChain[2], 64) . "-----END CERTIFICATE-----";
$rootPublicKey = JWKFactory::createFromCertificate($rootCert);

// 验证交易信息签名
$isTransactionValid = $jwsVerifier->verifyWithKey($transactionJws, $rootPublicKey, 0);
if ($isTransactionValid) {
    $transactionData = json_decode($transactionJws->getPayload(), true);
    print_r($transactionData); // 输出完整交易详情
}

关键注意事项

  • 必须使用原始JWS字符串进行签名验证,不能用解码后的数组,因为签名是针对整个JWS生成的
  • 证书必须转换为标准PEM格式,否则无法被库正常识别
  • 沙箱与生产环境的证书链不同,需注意区分环境
  • 签名验证是强制安全步骤,绝对不能跳过

内容的提问来源于stack exchange,提问作者Mihail Minkov

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.08 20:20:40