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

Golang调用PayPal沙箱Webhook验证接口始终返回FAILURE求助

PayPal沙箱Webhook验证失败(Golang环境)

问题背景

在Golang环境中调用PayPal沙箱的/v1/notifications/verify-webhook-signature接口验证Webhook,始终返回{"verification_status":"FAILURE"}。相关配置与参数如下:

  • 使用http.NewRequest发起POST请求,请求头已携带有效的access_token
  • 定义验证请求结构体:
var verifyData struct {
        AuthAlgo         string                 `json:"auth_algo"`
        CertUrl          string                 `json:"cert_url"`
        TransmissionId   string                 `json:"transmission_id"`
        TransmissionSig  string                 `json:"transmission_sig"`
        TransmissionTime string                 `json:"transmission_time"`
        WebhookEvent     map[string]interface{} `json:"webhook_event"`
        WebhookId        string                 `json:"webhook_id"`
    }
  • 参数来源:
    • WebhookEvent:从Webhook请求的http.Body()读取后经json.Unmarshal转为map[string]interface{}
    • 其余字段:从Webhook请求头获取(Paypal-Auth-Algo、Paypal-Cert-Url等)
    • webhook_id:沙箱应用Webhooks配置中获取

可能的失败原因及解决步骤

1. WebhookEvent序列化格式不一致

将原始Body转为map[string]interface{}再重新序列化时,可能出现字段顺序变更、数据类型转换(如数字转字符串)、空值处理差异等问题,导致与PayPal生成签名时的原始Payload不匹配。

解决方法:
直接保留原始Body的字节数据,避免二次序列化操作。可以将结构体中的WebhookEvent字段改为json.RawMessage类型,直接传入原始Body:

import (
    "io"
    "bytes"
    "encoding/json"
)

// 读取原始Webhook请求Body
rawBody, err := io.ReadAll(r.Body)
if err != nil {
    // 处理错误
}
// 恢复Body以便后续业务使用
r.Body = io.NopCloser(bytes.NewBuffer(rawBody))

// 重新定义验证结构体
var verifyData struct {
    AuthAlgo         string          `json:"auth_algo"`
    CertUrl          string          `json:"cert_url"`
    TransmissionId   string          `json:"transmission_id"`
    TransmissionSig  string          `json:"transmission_sig"`
    TransmissionTime string          `json:"transmission_time"`
    WebhookEvent     json.RawMessage `json:"webhook_event"`
    WebhookId        string          `json:"webhook_id"`
}

// 赋值字段
verifyData.AuthAlgo = r.Header.Get("PayPal-Auth-Algo")
verifyData.CertUrl = r.Header.Get("PayPal-Cert-Url")
verifyData.TransmissionId = r.Header.Get("PayPal-Transmission-Id")
verifyData.TransmissionSig = r.Header.Get("PayPal-Transmission-Sig")
verifyData.TransmissionTime = r.Header.Get("PayPal-Transmission-Time")
verifyData.WebhookEvent = rawBody
verifyData.WebhookId = "你的WebhookID"

// 序列化验证请求体
verifyReqBody, err := json.Marshal(verifyData)
if err != nil {
    // 处理错误
}

2. 请求头参数读取大小写错误

PayPal的Webhook请求头字段为驼峰大写开头(如PayPal-Auth-Algo而非Paypal-Auth-Algo),Golang的http.Request.Header.Get()方法区分大小写,需使用正确的字段名读取:

authAlgo := r.Header.Get("PayPal-Auth-Algo")
certUrl := r.Header.Get("PayPal-Cert-Url")
transmissionId := r.Header.Get("PayPal-Transmission-Id")
transmissionSig := r.Header.Get("PayPal-Transmission-Sig")
transmissionTime := r.Header.Get("PayPal-Transmission-Time")

3. Webhook ID不匹配

确认使用的webhook_id是沙箱应用中已启用状态的Webhook专属ID,而非应用ID或其他配置ID。可登录PayPal沙箱开发者后台,在Webhooks页面查看对应Webhook的ID。


手动签名验证实现(|||方式)

若API调用仍失败,可尝试手动验证签名,步骤如下:

步骤1:构造签名基础字符串

格式为:{transmissionId}|{transmissionTime}|{webhookId}|{crc32Checksum}
其中crc32Checksum是原始Webhook Body的CRC32校验值(无符号十六进制小写):

import (
    "hash/crc32"
    "fmt"
)

// 计算原始Body的CRC32校验值
crc32Hash := crc32.ChecksumIEEE(rawBody)
crc32Str := fmt.Sprintf("%x", crc32Hash)

// 构造签名基础字符串
signatureBase := fmt.Sprintf("%s|%s|%s|%s", transmissionId, transmissionTime, webhookId, crc32Str)

步骤2:解析PayPal公钥

从cert_url下载证书并解析出公钥:

import (
    "crypto/x509"
    "encoding/pem"
    "net/http"
    "io"
)

// 下载证书
resp, err := http.Get(certUrl)
if err != nil {
    // 处理错误
}
defer resp.Body.Close()
certData, err := io.ReadAll(resp.Body)
if err != nil {
    // 处理错误
}

// 解析证书获取公钥
block, _ := pem.Decode(certData)
if block == nil || block.Type != "CERTIFICATE" {
    // 处理错误
}
cert, err := x509.ParseCertificate(block.Bytes)
if err != nil {
    // 处理错误
}
publicKey := cert.PublicKey.(*rsa.PublicKey)

步骤3:验证签名

将transmission_sigBase64解码后,使用公钥和SHA256withRSA算法验证:

import (
    "crypto/rsa"
    "crypto/sha256"
    "crypto"
    "encoding/base64"
)

// 解码签名
sigBytes, err := base64.StdEncoding.DecodeString(transmissionSig)
if err != nil {
    // 处理错误
}

// 验证签名
hash := sha256.Sum256([]byte(signatureBase))
err = rsa.VerifyPKCS1v15(publicKey, crypto.SHA256, hash[:], sigBytes)
if err == nil {
    // 验证成功
} else {
    // 验证失败,输出错误信息排查
    fmt.Println("签名验证失败:", err)
}

额外排查建议

  • 确认access_token拥有https://uri.paypal.com/services/notifications/webhooks的权限
  • 检查API端点是否为沙箱环境:https://api.sandbox.paypal.com
  • 登录PayPal开发者后台查看Webhook日志,获取更详细的失败原因

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.28 11:53:12