Use Webhooks for event notifications
APICurrentLast updated: November 8th 2022, @ 10:41:15 am
PayPal REST APIs use webhooks for event notifications. Webhooks are push API calls that let your app know an event has happened.
How to use
Configure a webhook listener for your app and then create a webhook and subscribe it to the transaction events that you need.
Tip: You can verify your listener is working by using our webhooks simulator.
Messages
Each JSON-formatted POST
notification message contains event information based on the resource type and the event type. For example, an event that let your app know an authorization for payment occured would be of resource type authorization and an event type of created.
When your app receives a notification message, it must:
- Verify that the notification message came from PayPal
- Was not altered or corrupted during transmission
- Was targeted for you
- Contains a valid signature
- Respond with an HTTP
200
code
Note: If your app responds with any other status code, PayPal tries to resend the notification message 25 times over the course of three days.
Message signature
Because anyone could, theoretically, send a POST
to your app's listener, we sign our notification messages and send them over HTTPS (SSL/TLS). Event headers for notification messages contain the PayPal-generated asymmetric signature and information that you can use to validate the signature.
Event header generation
To generate the signature, PayPal concatenates and separates these items with the pipe (|
) character.
Event header | Description |
---|---|
PAYPAL-TRANSMISSION-SIG | The PayPal-generated asymmetric signature. |
PAYPAL-AUTH-ALGO | The algorithm that PayPal used to generate the signature and that you can use to verify the signature. |
PAYPAL-CERT-URL | The X509 public key certificate. Download the certificate from this URL and use it to verify the signature. |
The authentication algorithm specified in PAYPAL-AUTH-ALGO
uses an asymmetric signature algorithm, such as RSA with SHA256. This enables PayPal to use a private key to create the signature in PAYPAL-TRANSMISSION-SIG
and enables you to use a public key defined in PAYPAL-CERT-URL
to verify the webhook.
Event header validation
To validate a signature, use this input string:
<transmissionId>|<timeStamp>|<webhookId>|<crc32>
The fields in the string are:
Field | Description |
---|---|
transmissionId | The unique ID of the HTTP transmission from the PAYPAL-TRANSMISSION-ID header. |
timeStamp | The date and time when the HTTP message was transmitted from the PAYPAL-TRANSMISSION-TIME header. |
webhookId | The ID of the webhook resource for the destination URL to which PayPal delivers the event notification. |
crc32 | The Cyclic Redundancy Check (CRC32) checksum for the body of the HTTP payload. |
Note: When you validate the signature for notification messages that the Webhooks simulator generates, the webhook ID might vary depending on which method you used to simulate the event:
- If you used a webhook ID, use that same ID to validate the event.
- If you used a webhook URL, use
WEBHOOK_ID
to validate the event.
This Java pseudocode combines all headers and the input string to complete the verification:
// #Validate Webhook Sample
//
// This sample code demonstrates how to validate a webhook received on your
// web server. This sample assumes that you use the java servlet, which returns
// the HttpServletRequest object. However, you can modify this code to
// your specific case.
//
package com.paypal.api.payments.servlet;
import com.paypal.api.payments.CreditCard;
import com.paypal.api.payments.Event;
import com.paypal.api.payments.util.ResultPrinter;
import com.paypal.base.Constants;
import com.paypal.base.rest.APIContext;
import com.paypal.base.rest.PayPalRESTException;
import org.apache.log4j.Logger;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.security.InvalidKeyException;
import java.security.NoSuchAlgorithmException;
import java.security.SignatureException;
import java.util.Enumeration;
import java.util.HashMap;
import java.util.Map;
import static com.paypal.api.payments.util.SampleConstants.*;
public class ValidateWebhookServlet extends HttpServlet {
private static final long serialVersionUID = 1 L;
private static final Logger LOGGER = Logger.getLogger(ValidateWebhookServlet.class);
public static final String WebhookId = "4JH86294D6297924G";
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp)
throws ServletException, IOException {
doPost(req, resp);
}
// ##Validate Webhook
@Override
protected void doPost(HttpServletRequest req, HttpServletResponse resp)
throws ServletException, IOException {
try {
// ### Api Context
APIContext apiContext = new APIContext(clientID, clientSecret, mode);
// Set the webhookId that you received when you created this webhook.
apiContext.addConfiguration(Constants.PAYPAL_WEBHOOK_ID, WebhookId);
Boolean result = Event.validateReceivedEvent(apiContext, getHeadersInfo(
req), getBody(req));
System.out.println("Result is " + result);
LOGGER.info("Webhook Validated: " + result);
ResultPrinter.addResult(req, resp, "Webhook Validated: ", CreditCard.getLastRequest(),
CreditCard.getLastResponse(), null);
} catch (PayPalRESTException e) {
LOGGER.error(e.getMessage());
ResultPrinter.addResult(req, resp, "Webhook Validated: ", CreditCard.getLastRequest(),
null, e.getMessage());
} catch (InvalidKeyException e) {
LOGGER.error(e.getMessage());
ResultPrinter.addResult(req, resp, "Webhook Validated: ", CreditCard.getLastRequest(),
null, e.getMessage());
} catch (NoSuchAlgorithmException e) {
LOGGER.error(e.getMessage());
ResultPrinter.addResult(req, resp, "Webhook Validated: ", CreditCard.getLastRequest(),
null, e.getMessage());
} catch (SignatureException e) {
LOGGER.error(e.getMessage());
ResultPrinter.addResult(req, resp, "Webhook Validated: ", CreditCard.getLastRequest(),
null, e.getMessage());
}
}
// Simple helper method to help you extract the headers from HttpServletRequest object.
private static Map < String, String > getHeadersInfo(HttpServletRequest request) {
Map < String, String > map = new HashMap < String, String > ();
@SuppressWarnings("rawtypes")
Enumeration headerNames = request.getHeaderNames();
while (headerNames.hasMoreElements()) {
String key = (String) headerNames.nextElement();
String value = request.getHeader(key);
map.put(key, value);
}
return map;
}
// Simple helper method to fetch request data as a string from HttpServletRequest object.
private static String getBody(HttpServletRequest request) throws IOException {
String body;
StringBuilder stringBuilder = new StringBuilder();
BufferedReader bufferedReader = null;
try {
InputStream inputStream = request.getInputStream();
if (inputStream != null) {
bufferedReader = new BufferedReader(new InputStreamReader(inputStream));
char[] charBuffer = new char[128];
int bytesRead = -1;
while ((bytesRead = bufferedReader.read(charBuffer)) > 0) {
stringBuilder.append(charBuffer, 0, bytesRead);
}
} else {
stringBuilder.append("");
}
} catch (IOException ex) {
throw ex;
} finally {
if (bufferedReader != null) {
try {
bufferedReader.close();
} catch (IOException ex) {
throw ex;
}
}
}
body = stringBuilder.toString();
return body;
}
}
Sample message payload
The sample here is a notification that let your app know an authorization for payment occurred.
{
"id": "8PT597110X687430LKGECATA",
"create_time": "2013-06-25T21:41:28Z",
"resource_type": "authorization",
"event_type": "PAYMENT.AUTHORIZATION.CREATED",
"summary": "A payment authorization was created",
"resource": {
"id": "2DC87612EK520411B",
"create_time": "2013-06-25T21:39:15Z",
"update_time": "2013-06-25T21:39:17Z",
"state": "authorized",
"amount": {
"total": "7.47",
"currency": "USD",
"details": {
"subtotal": "7.47"
}
},
"parent_payment": "PAY-36246664YD343335CKHFA4AY",
"valid_until": "2013-07-24T21:39:15Z",
"links": [
{
"href": "https://api-m.sandbox.paypal.com/v1/payments/authorization/2DC87612EK520411B",
"rel": "self",
"method": "GET"
},
{
"href": "https://api-m.sandbox.paypal.com/v1/payments/authorization/2DC87612EK520411B/capture",
"rel": "capture",
"method": "POST"
},
{
"href": "https://api-m.sandbox.paypal.com/v1/payments/authorization/2DC87612EK520411B/void",
"rel": "void",
"method": "POST"
},
{
"href": "https://api-m.sandbox.paypal.com/v1/payments/payment/PAY-36246664YD343335CKHFA4AY",
"rel": "parent_payment",
"method": "GET"
}]
},
"links": [
{
"href": "https://api-m.sandbox.paypal.com/v1/notifications/webhooks-events/8PT597110X687430LKGECATA",
"rel": "self",
"method": "GET"
},
{
"href": "https://api-m.sandbox.paypal.com/v1/notifications/webhooks-events/8PT597110X687430LKGECATA/resend",
"rel": "resend",
"method": "POST"
}]
}
Note: To get the latest resource from PayPal, navigate to the HATEOAS
self
link from the received payload.