Search code examples
c#paypal-ipnpaypal-subscriptionspaypal-webhooks

PayPal Webhook never return VERIFIED


I am unable to get paypal to verify the webhook request through the webhooks simulator. The only difference between my VerifyTask and the example is that I converted from webclient to HttpClient. This code is as close to the example ipn listener from the paypal site. The following is c# asp.net code:

public class IPNSendBack
{
    public HttpRequest? IPNRequest { get; set; }
    public string? RequestBody { get; set; }
    public string? Verification { get; set; }
}
[AllowAnonymous]
[HttpPost()]
public async Task<IActionResult> ReceiveWebHook()
{
    var headers = HttpContext.Request.Headers;
    string transmissionid = headers["Paypal-Transmission-Id"].ToString();
    IPNSendBack ipnContext = new IPNSendBack()
    {
        IPNRequest = Request
    };
    using (StreamReader reader = new StreamReader(ipnContext.IPNRequest.Body, Encoding.ASCII))
    {
        ipnContext.RequestBody = await reader.ReadToEndAsync();
    }
    await Task.Run(() => VerifyTask(ipnContext, transmissionid));   
}
private async Task VerifyTask(IPNSendBack ipnContext, string? transmissionid)
{
    string url = isSandbox
           ? "https://ipnpb.sandbox.paypal.com/cgi-bin/webscr"
           : "https://ipnpb.paypal.com/cgi-bin/webscr";

    using (HttpClient client = new HttpClient())
    {
        // Set values for the verification request
        var content = new StringContent($"cmd=_notify-validate&{ipnContext.RequestBody}", Encoding.ASCII, "application/x-www-form-urlencoded");

        // Send the request to PayPal and get the response
        HttpResponseMessage response = await client.PostAsync(url, content);
        response.EnsureSuccessStatusCode(); // Ensure successful response
        if (response.IsSuccessStatusCode)
        {
            string verificationResult = await response.Content.ReadAsStringAsync();
            ipnContext.Verification = verificationResult;
        }
    }
    await ProcessVerificationResponse(ipnContext, transmissionid);
}

Aside from the always getting the INVALID response, I am able to get what I need and can properly process the response body, so I know the request body is good, at least from the simulator.

Based on the response, I have modified my code as follows:

public async Task<IActionResult> RecieveWebHook()
{
    APICredentials credentials = new APICredentials(Mode);
    string payload;
    using (StreamReader reader = new StreamReader(Request.Body, Encoding.UTF8))
    {
        payload = await reader.ReadToEndAsync();
    }
    //var content = new StringContent(payload, Encoding.UTF8, "application/json");
    VerifyObj vo = new VerifyObj()
    {
        transmission_id = requestheaders["Paypal-Transmission-Id"].ToString(),
        transmission_time = requestheaders["Paypal-Transmission-Time"].ToString(),
        cert_url = requestheaders["Paypal-Cert-Url"].ToString(),
        auth_algo = requestheaders["Paypal-Auth-Algo"].ToString(),
        transmission_sig = requestheaders["Paypal-Transmission-Sig"].ToString(),
        webhook_id = credentials.WebHookId,
        webhook_event = payload
    };
    string data = JsonSerializer.Serialize(vo);

    HttpRequestMessage requestMsg = new HttpRequestMessage(HttpMethod.Post, credentials.Url);
    requestMsg.Headers.Add("accept","application/json");
    //requestMsg.Headers.Add("Authorization", $"Bearer {credentials.Secret}");
    requestMsg.Content = new StringContent(data, Encoding.UTF8, "application/json");
    try
    {
        using (HttpClient client = new HttpClient())
        {                    
            var response = await client.SendAsync(requestMsg);
            if (response.IsSuccessStatusCode)
            {
                string verificationResult = await response.Content.ReadAsStringAsync();
            }
        }
    }
    catch (Exception ex)
    {
    }       
    return Ok();
}

it is still not validating. I commented out the Authorization Header, clearly my webhook secret is not what it is looking for. What is it and where do I get it?


Solution

  • PayPal Webhooks and IPN (Instant Payment Notification) are two different services. There is no connection between the two. A simple way to distinguish them is that Webhooks are always in JSON format, and IPN messages are always in NVP or name-value pair format (&name=value).

    Assuming you are interested in validating a PayPal Webhook message in JSON format, there are two methods.

    1. Cryptographic verification using a hash of the webhook message payload. Java pseudocode for this operation is in the Webhooks Guide.
    2. Post the webhook JSON message body (unmodified, in the exact order you received it) along with metadata from the HTTP headers as well as the 17 digit id from when you first created/registered for the webhook (not the WH-#-# event id) to the webhook signature verification endpoint, as detailed in the Webhooks API reference. It will check the hash signature for you.

    The code in your question appears to be for IPN, which again is irrelevant to PayPal Webhooks.