Webhooks are a system of automated notifications that push information to your designated destination when important events occur.
To create a webhook in your Loon Service Panel:
- Click Developers in the main navigation.
- Click the Loon Webhooks tab.
- Click Add Webhook.
- Add your webhook endpoint URL.
- Add your own SecretKey or use the suggested SecretKey provided by Loon. Save this for validating webhooks in the future.
- Click Save.
You can navigate to the Webhooks page in your Loon Service Panel at any time to review and edit existing webhooks, or add new webhooks as needed.
Keep in Mind:
- If a single webhook has five consecutive failures, we will set the webhook status to inactive. This only happens if the failures are consecutive (not intermittent)
- If a webhook fails or is set to inactive, navigate to the Webhooks page in your Loon Service Panel to edit and re-enable the webhook
Webhook Types
You will receive the following types of webhook notifications from Loon when a job status changes:
Job Created
This notification alerts you that the job was successful; the webhook includes an associated job_id
{
"type": "wh_job_created",
"data": {
"merchantId": "9bb8592c-cb99-48f7-907e-f97de930fc5c",
"job_id": 98765,
"timestamp": "2023-11-16T20:15:45.678Z"
}
}
Job Complete
This notification alerts you that the job was successful.
{
"type": "wh_job_completed",
"data": {
"merchantId": "9bb8592c-cb99-48f7-907e-f97de930fc5c",
"job_id": 98765,
"timestamp": "2023-11-16T23:15:45.678Z"
}
}
Job Failed
This notification alerts you that the job was not successful. This webhook includes a metadata
property in the data.error_details []
array that contains the value of the metadata for the row in the submitted Loon job file that produced the error.
{
"type": "wh_job_failed",
"data": {
"merchantId": "9bb8592c-cb99-48f7-907e-f97de930fc5c",
"job_id": 54321,
"reason": "validation_failed",
"error_details": [
{
"detail": "file_validation_network_invalid",
"additional_info": "A duplicate account number was found on line number 4345."
"metadata": "[value from the job file for the row]"
}
],
"timestamp": "2023-11-16T22:30:45.678Z"
}
}
Learn more about this webhook in our Loon Error Handling guide.
Job Status Changed
This notification alerts you that the job's status has changed (e.g. pending
). If the status is error
, the metadata
property in the details []
array will contain the value of the metadata for the row in the submitted Loon job file that produced the error.
{
"status": "error",
"error": {
"code": "validation_failed",
"message": "The file failed to validate. See Details for details.",
"details": [
{
"merchantId": "9bb8592c-cb99-48f7-907e-f97de930fc5c",
"detail": "file_validation_month_invalid",
"additional_info": "Row = 1, Reason = Invalid length of a expiry_month"
"metadata": "[value from the job file for the row]"
}
]
},
"networks": [
{
"network": "visa",
"status": "pending"
},
{
"network": "discover",
"status": "error"
}
]
}
See our Loon Error Handling guide for more information on how to proceed after receiving this webhook with an error
status.
Network Unprocessable
This notification identifies which card brand was inaccessible at the time the job was run
{
"type": "wh_network_unprocessable",
"data": {
"merchantId": "9bb8592c-cb99-48f7-907e-f97de930fc5c",
"job_id": 54321,
"network": "SampleNetwork",
"cards_amount": 10,
"timestamp": "2023-11-16T19:30:15.789Z"
}
}
Network Processed
This notification identifies which card brand was successfully processed
{
"type": "wh_network_processed",
"data": {
"merchantId": "9bb8592c-cb99-48f7-907e-f97de930fc5c",
"network": "visa",
"job_id": 12345,
"timestamp": "2023-11-16T12:34:56.789Z"
}
}
Validating webhooks
When you first set up a webhook in the Loon Service Panel, you'll create a SecretKey for it. Save each SecretKey somewhere secure for use in validating webhooks from Pagos moving forward. We will not display your secret again; if you ever lose it, create a new one by editing the webhook in the Loon Service Panel.
The header of each webhook you receive includes a x-pagos-signature
property containing an HMAC-SHA256 generated hash signature. We recommend always validating this signature to ensure your server only processes webhook deliveries sent by Pagos and to verify the delivery hasn't been tampered with. This will help you avoid using server resources to process deliveries, or updating your source-of-truth systems based on messages that do not originate from Pagos, thereby helping to prevent man-in-the-middle attacks.
The x-pagos-signature
contains the webhook signature in the following format:
{
t={timestamp in unix time},{signature-version}={signature}
}
Pagos generates the webhook signature hash as a concatenation of the timestamp value in t=
from the x-pagos-signature
, a period (“.”), the contents of the body:
of the webhook payload, and the stored customer secret. This signature will be sent in the {signature-version}=
property of the x-pagos-signature
. The {signature-version}
will be initially set as “V1”, representing SHA256. If additional hashing algorithms are offered, then an additional {signature-version}
will be created representing these additional hashing algorithms.
To validate the webhook signature a customer should generate a webhook signature using the above process of concatenation of the timestamp value in t=
from the x-pagos-signature
plus a period “.” plus the contents of the body:
of the webhook payload and the stored customer secret. Then compare your signature with the signature value in {signature-version}={signature}
. If they match, you're safe to process the webhook. If they don’t match, then the webhook should be dropped.
Example Webhook Message
{
x-pagos-signature: t=1731326247,v1=K1dEDpPNgRiehBEZzyx1/mZYKjE0jrK3qkvklPqAG+g=
body:
{
"type":"wh_job_created",
"data":{
"job_id":23255,
"timestamp":"2024-11-11T11:57:22.6315227Z",
"merchant_id":"ae89c6af-dde7-4460-8a6f-bd64ec0826a6"
}
}
}
Python Code for Generating and Comparing Signatures
import hmac
import hashlib
import base64
signature = 'K1dEDpPNgRiehBEZzyx1/mZYKjE0jrK3qkvklPqAG+g='
time_stamp = '1731326247'
message_body = '{"type":"wh_job_created","data":{"job_id":23255,"timestamp":"2024-11-11T11:57:22.6315227Z","merchant_id":"ae89c6af-dde7-4460-8a6f-bd64ec0826a6"}}'
message = time_stamp + '.' + message_body
secret_key = "RAJZ5nBM,)Ub]eUw7cXwD%]hN<tHIIYR#2%Tv[FS6Ad_[{y[;@#sh2<><8HrEd>r"
# Create an HMAC object using SHA-256
hmac_object = hmac.new(secret_key.encode('utf-8'), message.encode('utf-8'), hashlib.sha256)
# Get the HMAC signature as a hexadecimal string
base64_string = base64.b64encode(hmac_object.digest()).decode("utf-8")
result = base64_string == signature
print(f"The signatures match: {result}")
Note
Webhook verification is highly recommended, but isn't required. We also advise (but don't require) you to allow-list the Pagos IP address that sends webhooks to your system as an additional security measure.