无法执行成功的 Paypal webhook 验证
Unable to perform successful Paypal webhook validation
我正在努力验证 Paypal webhook 数据,但我 运行 遇到了一个问题,它总是返回验证状态失败。我想知道是不是因为这一切都发生在沙盒环境中,而 Paypal 不允许验证沙盒 webhook 事件?我按照这个 API 文档来实现调用:https://developer.paypal.com/docs/api/webhooks/v1/#verify-webhook-signature
相关代码(来自单独的 elixir 模块):
def call(conn, _opts) do
conn
|> extract_webhook_signature(conn.params)
|> webhook_signature_valid?()
|> # handle the result
end
defp extract_webhook_signature(conn, params) do
%{
auth_algo: get_req_header(conn, "paypal-auth-algo") |> Enum.at(0, ""),
cert_url: get_req_header(conn, "paypal-cert-url") |> Enum.at(0, ""),
transmission_id: get_req_header(conn, "paypal-transmission-id") |> Enum.at(0, ""),
transmission_sig: get_req_header(conn, "paypal-transmission-sig") |> Enum.at(0, ""),
transmission_time: get_req_header(conn, "paypal-transmission-time") |> Enum.at(0, ""),
webhook_id: get_webhook_id(),
webhook_event: params
}
end
def webhook_signature_valid?(signature) do
body = Jason.encode!(signature)
case Request.post("/v1/notifications/verify-webhook-signature", body) do
{:ok, %{verification_status: "SUCCESS"}} -> true
_ -> false
end
end
我从 Paypal 收到了 200,这意味着 Paypal 收到了我的请求并能够正确解析它并且 运行 它通过了验证,但它总是返回验证状态失败,这意味着无法验证请求的真实性。我查看了我发布到他们端点的数据,一切看起来都是正确的,但出于某种原因,它没有得到验证。我将发布到 API(来自 extract_webhook_signature
)的 JSON 放入此处的 Pastebin,因为它非常大:https://pastebin.com/SYBT7muv
如果有人对此有经验并且知道它为什么会失败,我很想听听。
我解决了我自己的问题。 Paypal 不会规范化他们的 webhook 验证请求。当您从 Paypal 收到 POST 时,在验证调用中将其发回给他们之前,请勿解析请求正文。如果您的 webhook_event
有任何不同(即使这些字段的顺序不同),该事件将被视为无效并且您将收到失败消息。您必须在 webhook_event
.
中读取原始 POST 正文和 post 精确 数据返回到 Paypal
示例:
如果您收到 {"a":1,"b":2}
并且您 post 返回 {..., "webhook_event":{"b":2,"a":1}, ...}
(请注意 json 字段的顺序与我们收到的和我们 post 返回的不同)你会收到一个失败。您的 post 需要 {..., "webhook_event":{"a":1,"b":2}, ...}
对于那些为此苦苦挣扎的人,我想为您提供我的解决方案,其中包括已接受的答案。
开始之前,请确保将 raw_body
存储在您的连接中,如 Verifying the webhook - the client side
中所述
@verification_url "https://api-m.sandbox.paypal.com/v1/notifications/verify-webhook-signature"
@auth_token_url "https://api-m.sandbox.paypal.com/v1/oauth2/token"
defp get_auth_token do
headers = [
Accept: "application/json",
"Accept-Language": "en_US"
]
client_id = Application.get_env(:my_app, :paypal)[:client_id]
client_secret = Application.get_env(:my_app, :paypal)[:client_secret]
options = [
hackney: [basic_auth: {client_id, client_secret}]
]
body = "grant_type=client_credentials"
case HTTPoison.post(@auth_token_url, body, headers, options) do
{:ok, %HTTPoison.Response{status_code: 200, body: body}} ->
%{"access_token" => access_token} = Jason.decode!(body)
{:ok, access_token}
error ->
Logger.error(inspect(error))
{:error, :no_access_token}
end
end
defp verify_event(conn, auth_token, raw_body) do
headers = [
"Content-Type": "application/json",
Authorization: "Bearer #{auth_token}"
]
body =
%{
transmission_id: get_header(conn, "paypal-transmission-id"),
transmission_time: get_header(conn, "paypal-transmission-time"),
cert_url: get_header(conn, "paypal-cert-url"),
auth_algo: get_header(conn, "paypal-auth-algo"),
transmission_sig: get_header(conn, "paypal-transmission-sig"),
webhook_id: Application.get_env(:papervault, :paypal)[:webhook_id],
webhook_event: "raw_body"
}
|> Jason.encode!()
|> String.replace("\"raw_body\"", raw_body)
with {:ok, %{status_code: 200, body: encoded_body}} <-
HTTPoison.post(@verification_url, body, headers),
{:ok, %{"verification_status" => "SUCCESS"}} <- Jason.decode(encoded_body) do
:ok
else
error ->
Logger.error(inspect(error))
{:error, :not_verified}
end
end
defp get_header(conn, key) do
conn |> get_req_header(key) |> List.first()
end
我正在努力验证 Paypal webhook 数据,但我 运行 遇到了一个问题,它总是返回验证状态失败。我想知道是不是因为这一切都发生在沙盒环境中,而 Paypal 不允许验证沙盒 webhook 事件?我按照这个 API 文档来实现调用:https://developer.paypal.com/docs/api/webhooks/v1/#verify-webhook-signature
相关代码(来自单独的 elixir 模块):
def call(conn, _opts) do
conn
|> extract_webhook_signature(conn.params)
|> webhook_signature_valid?()
|> # handle the result
end
defp extract_webhook_signature(conn, params) do
%{
auth_algo: get_req_header(conn, "paypal-auth-algo") |> Enum.at(0, ""),
cert_url: get_req_header(conn, "paypal-cert-url") |> Enum.at(0, ""),
transmission_id: get_req_header(conn, "paypal-transmission-id") |> Enum.at(0, ""),
transmission_sig: get_req_header(conn, "paypal-transmission-sig") |> Enum.at(0, ""),
transmission_time: get_req_header(conn, "paypal-transmission-time") |> Enum.at(0, ""),
webhook_id: get_webhook_id(),
webhook_event: params
}
end
def webhook_signature_valid?(signature) do
body = Jason.encode!(signature)
case Request.post("/v1/notifications/verify-webhook-signature", body) do
{:ok, %{verification_status: "SUCCESS"}} -> true
_ -> false
end
end
我从 Paypal 收到了 200,这意味着 Paypal 收到了我的请求并能够正确解析它并且 运行 它通过了验证,但它总是返回验证状态失败,这意味着无法验证请求的真实性。我查看了我发布到他们端点的数据,一切看起来都是正确的,但出于某种原因,它没有得到验证。我将发布到 API(来自 extract_webhook_signature
)的 JSON 放入此处的 Pastebin,因为它非常大:https://pastebin.com/SYBT7muv
如果有人对此有经验并且知道它为什么会失败,我很想听听。
我解决了我自己的问题。 Paypal 不会规范化他们的 webhook 验证请求。当您从 Paypal 收到 POST 时,在验证调用中将其发回给他们之前,请勿解析请求正文。如果您的 webhook_event
有任何不同(即使这些字段的顺序不同),该事件将被视为无效并且您将收到失败消息。您必须在 webhook_event
.
示例:
如果您收到 {"a":1,"b":2}
并且您 post 返回 {..., "webhook_event":{"b":2,"a":1}, ...}
(请注意 json 字段的顺序与我们收到的和我们 post 返回的不同)你会收到一个失败。您的 post 需要 {..., "webhook_event":{"a":1,"b":2}, ...}
对于那些为此苦苦挣扎的人,我想为您提供我的解决方案,其中包括已接受的答案。
开始之前,请确保将 raw_body
存储在您的连接中,如 Verifying the webhook - the client side
@verification_url "https://api-m.sandbox.paypal.com/v1/notifications/verify-webhook-signature"
@auth_token_url "https://api-m.sandbox.paypal.com/v1/oauth2/token"
defp get_auth_token do
headers = [
Accept: "application/json",
"Accept-Language": "en_US"
]
client_id = Application.get_env(:my_app, :paypal)[:client_id]
client_secret = Application.get_env(:my_app, :paypal)[:client_secret]
options = [
hackney: [basic_auth: {client_id, client_secret}]
]
body = "grant_type=client_credentials"
case HTTPoison.post(@auth_token_url, body, headers, options) do
{:ok, %HTTPoison.Response{status_code: 200, body: body}} ->
%{"access_token" => access_token} = Jason.decode!(body)
{:ok, access_token}
error ->
Logger.error(inspect(error))
{:error, :no_access_token}
end
end
defp verify_event(conn, auth_token, raw_body) do
headers = [
"Content-Type": "application/json",
Authorization: "Bearer #{auth_token}"
]
body =
%{
transmission_id: get_header(conn, "paypal-transmission-id"),
transmission_time: get_header(conn, "paypal-transmission-time"),
cert_url: get_header(conn, "paypal-cert-url"),
auth_algo: get_header(conn, "paypal-auth-algo"),
transmission_sig: get_header(conn, "paypal-transmission-sig"),
webhook_id: Application.get_env(:papervault, :paypal)[:webhook_id],
webhook_event: "raw_body"
}
|> Jason.encode!()
|> String.replace("\"raw_body\"", raw_body)
with {:ok, %{status_code: 200, body: encoded_body}} <-
HTTPoison.post(@verification_url, body, headers),
{:ok, %{"verification_status" => "SUCCESS"}} <- Jason.decode(encoded_body) do
:ok
else
error ->
Logger.error(inspect(error))
{:error, :not_verified}
end
end
defp get_header(conn, key) do
conn |> get_req_header(key) |> List.first()
end