A Webhook is an HTTP callback that occurs when something happens; a simple event-notification system via HTTP POST that allows developers to easily access notifications of payment activities, such as payment status update, or recurring charges. You can perform actions on your back-end after processing each notification, such as:
- Email a purchase confirmation to your customer.
- Enable download of digital media.
- Issue a refund.
- Keep track of which subscriptions are active.
To create a Webhook, navigate to the PayPal Dashboard, and click on My Apps & Credentials. Then select the app in which you want to set up the Webhooks.
You can see the details about your application. Notice in the top right that there are two buttons (Sandbox, Live), I'll be using Sandbox during this tutorial, but you need to set up your Live settings before going Live. To configure Webhooks for this app, click on Add Webhook as displayed in the screenshot:
Select the Event Types that you are interested in getting notified, and enter the URL where the Webhooks will be sent to (it has to be HTTPS). To handle the Webhook calls, I'm going to add a new action method to the HomeController called 'Webhook':
1
2
3
4
| public IActionResult Webhook() { // TODO: Handle Webhook call } |
So the Webhook Url in this case will be:https://pedroalonso.localtunnel.me/home/webhook. I'll explain the 'localtunnel' part in the next section.
When you save the Webhook, you will see this confirmation screen:
Now that the Webhook is set up, you can see in the left menu under 'Webhooks Simulator', here you can send 'test' webhook events to your URL to test that your code is working. Also, under 'Webhooks Events' you can see all the events that PayPal sent for this application. You can verify that you're handling the events correctly and re-send them if you want to do further testing.
To see how the Webhooks are working, I ran the project that we built in the previous tutorial, and I created an 'authorize payment and capture later', so that PayPal would send the event. After running the sample, I clicked on 'Webhooks Event' and I can see that the event has been sent:
As you can see, at the right there is aResend button if you want to debug your code and see how to properly implement the handler. Also, if you click on the event, you can see all the details:
This is the full JSON for the Webhook Event:
01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
| { "id" : "WH-9U51749144910293K-8LX80763BC1567402" , "create_time" : "2016-01-19T17:50:30Z" , "resource_type" : "sale" , "event_type" : "PAYMENT.SALE.COMPLETED" , "summary" : "Payment completed for $ 100.0 USD" , "resource" : { "amount" : { "total" : "100.00" , "currency" : "USD" , "details" : { "subtotal" : "100.00" , "tax" : "15.00" , "shipping" : "10.00" } }, "id" : "73G8209522783053E" , "parent_payment" : "PAY-7MB27930V5981832YK2PHN7Q" , "update_time" : "2016-01-19T17:49:05Z" , "create_time" : "2016-01-19T17:49:05Z" , "payment_mode" : "INSTANT_TRANSFER" , "state" : "completed" , "links" : [ { "rel" : "self" , "method" : "GET" }, { "rel" : "refund" , "method" : "POST" }, { "rel" : "parent_payment" , "method" : "GET" } ], "protection_eligibility_type" : "ITEM_NOT_RECEIVED_ELIGIBLE,UNAUTHORIZED_PAYMENT_ELIGIBLE" , "transaction_fee" : { "value" : "3.20" , "currency" : "USD" }, "protection_eligibility" : "ELIGIBLE" }, "status" : "PENDING" , "transmissions" : [ { "response_headers" : { "Date" : "Wed, 20 Jan 2016 12:53:51 GMT" , "Content-Length" : "53" , "HTTP/1.1 502 Bad Gateway" : "" , "SERVER_INFO" : "" , "Connection" : "keep-alive" , "Server" : "nginx/1.7.8" }, "transmission_id" : "218dc9c0-bed5-11e5-927f-6b62a8a99ac4" , "status" : "PENDING" , "timestamp" : "2016-01-19T17:50:30Z" } ], "links" : [ { "rel" : "self" , "method" : "GET" , "encType" : "application/json" }, { "rel" : "resend" , "method" : "POST" , "encType" : "application/json" } ] } |
As you can see in the picture, the event details are encoded in JSON and they're sent as the body of the request to your Webhook URL handler. Also, there are several important properties that we need to use in our handler:
- id: This is the id of the webhook event, and we need to send this parameter to PayPal if we want to retrieve a specific webhook event.
- event_type: This is used to know the type of event that we are receiving as we probably need to process different event types in different ways.
- resource.parent_payment: This is the id of the payment that this event is related to. We possibly have this id stored in a database, and can send an email to our customer or ship the goods purchased by the customer.
Based on the previous explanation, this is the code of the action controller to process the Webhook:
01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
| public IActionResult Webhook() { // The APIContext object can contain an optional override for the trusted certificate. var apiContext = PayPalConfiguration.GetAPIContext(); // Get the received request's headers var requestheaders = HttpContext.Request.Headers; // Get the received request's body var requestBody = string .Empty; using ( var reader = new System.IO.StreamReader(HttpContext.Request.Body)) { requestBody = reader.ReadToEnd(); } dynamic jsonBody = JObject.Parse(requestBody); string webhookId = jsonBody.id; var ev = WebhookEvent.Get(apiContext, webhookId); // We have all the information the SDK needs, so perform the validation. // Note: at least on Sandbox environment this returns false. // var isValid = WebhookEvent.ValidateReceivedEvent(apiContext, ToNameValueCollection(requestheaders), requestBody, webhookId); switch (ev.event_type) { case "PAYMENT.CAPTURE.COMPLETED" : // Handle payment completed break ; case "PAYMENT.CAPTURE.DENIED" : // Handle payment denied break ; // Handle other webhooks default : break ; } return new HttpStatusCodeResult(200); } |
A few things to explain from the previous function. From lines 10-16, I'm only reading the body of the request and parsing the JSON object to a dynamic C# object. On line 18, which is optional, I'm calling the PayPal API with the event Id to get the full event details. This is done for security to verify that I'm using a valid PayPal object. On line 24 I have created a switch to evaluate the types of Webhooks that I want to process and write the custom code as needed.
As you can also see, line 22 is commented out. Apparently that method validates that the SSL certificate from the Request is valid and belongs to PayPal, but it doesn't work in Sandbox mode. It might work in Live, but I don't like to have code in Live that is not tested, especially if it's dealing with a Payment Gateway, so I opted for removing that and using a different approach. If you use the PHP version of the PayPal SDK library, bear in mind that the function 'ValidateReceivedEvent' does not even exist.
Testing Webhooks: Secure Tunnel
As you have seen previously, in order to test Webhooks, we need to configure a public URL that PayPal can use to send the events. If we are working locally, normally we develop using 'localhost', so that would be a small problem. To solve that, we need to configure a secure tunnel to our local computer.
Localtunnel is a small piece of software that creates a secure tunnel between your local machine and a publicly accessible domain. It’s useful for testing Webhooks, but you can also use it to share live URLs to web applications running on your development machine for the purposes of testing, feedback, or other tasks.
You need to have Node.js to be able to install localtunnel. Then simply open a console or terminal, and run:
$ npm install -g localtunnel
To create a tunnel, run:
$ lt --port 5000 --subdomain pedroalonso
That will map the URL 'https://pedroalonso.localtunnel.me' to 'localhost:5000'. If you run your project on IIS Express, you probably will use a different port, so you'll need to reflect that in your command.
After localtunnel is set up and our project is running, I have added a breakpoint in Visual Studio to evaluate the data that I'm getting. As you can see in this screenshot, I have mapped the JSON event object to a C# dynamic object.
Retrieving the event from PayPal API using the event Id, we also get the event details, as you can see here:
Conclusion
Webhooks are becoming a standard way for REST API to notify applications about events. As you can see, they are pretty easy to handle, and they are used by many companies such as Stripe, SendGrid, MailChimp, etc. PayPal used to have Instant Payment Notification, and it's still in use, but they recommend implementing Webhooks whenever possible.
I think it would be really interesting if more consumer applications offered Webhooks too. The ability to start a process based upon an event in a separate application is extremely useful and offers a glimpse at the future of the real-time web.
Comments
Post a Comment