Skip to main content

Listen for Webhook Events

Create a webhook subscription, configure your endpoint to respond and verify signatures, and allowlist PayLoco IPs to receive webhook notifications. For an overview of how webhooks work, delivery behavior, and security, see the Webhooks Overview.

Before You Begin

You must have the Developer, Admin, or Owner role in the PayLoco Web App.

Create a Webhook Subscription

Subscribe to webhooks by registering a notification URL and selecting the events you want to receive. When one of those events occurs on your account, PayLoco sends a JSON payload to your URL via HTTP POST. Steps to create a webhook subscription:
  1. Log in to the PayLoco Web App and go to Developer > Webhooks.
  2. Click New webhook and configure the notification URL and the events to listen for.
  3. Fill in the following fields on the Create webhook page:
    • Name: The name of your webhook subscription.
    • Notification URL: The URL to receive webhook notifications.
    • API version: The API version used by the webhook, which determines the structure of notifications you receive, including event names and payload fields. Defaults to the API version configured for your account. Create a webhook
  4. Select the events for the webhook to listen for.
  5. Click Submit to create the webhook subscription.
[Info] You can preview a sample payload by selecting an individual webhook event.

Delivery Headers

HTTP POST payloads sent to your webhook notification URL include the following special headers:
HeaderDescription
x-timestampUnix timestamp in milliseconds, e.g. 1357872222592.
x-signatureHMAC hex digest of the request body. Sent when the webhook is configured with a secret. The digest is generated using the SHA-256 hash function with the secret as the HMAC key.

Webhook Payload

The webhook payload is sent as JSON in the POST request body. The full details of the event are included and can be used directly after parsing the JSON into an Event object. The id field corresponds to the event ID in the Web App. The Event object contains the following fields:
FieldDescription
orderIdUnique identifier for the event.
nameEvent type, e.g. payment_attempt.authorized.
merchantIdUnique identifier of the merchant this event belongs to.
dataContains business information that varies by event type. See individual event type pages for sample payloads.
notifyTimeTime the event was created.
{
    "code": "00000000",
    "message": "Success",
    "traceId": "85776c0dc17b4911bc38f6c24bf98504",
    "merchantId": "851244000040",
    "notifyTime": "2026-06-16T18:14:28.26137491+08:00",
    "notifyType": "PAYMENT",
    "data": {
        "merchantOrderId": "ORD1781604849792920",
        "orderId": "1937771703079430",
        "totalAmount": "10.01",
        "currency": "HKD",
        "country": "HK",
        "status": "SUCCESS",
        "completeTime": "2026-06-16T18:14:28.177+08:00",
        "paymentDetails": {
            "paymentMethodType": "WALLET",
            "targetOrg": "ALIPAYHK"
        },
        "reference": "goods reference",
        "cashierCountry": "HK"
    }
}

Respond to Webhook Events

Your endpoint must acknowledge all notifications by returning an HTTP 200 OK status code with the following response body. If PayLoco does not receive a 200 OK response (due to timeout or any other status code), the delivery is considered failed and will be retried. To prevent timeouts, send the response immediately.
{
  "code": "00000000",
  "message": "Success"
}

Verify Webhook Signatures

PayLoco attaches a signature to the webhook request header sent to your notification URL so you can verify that the event was sent by PayLoco. Before verifying signatures, retrieve the secret key for your notification URL from the Web App. Each secret is uniquely bound to its corresponding URL. Once configured, PayLoco will sign every webhook sent to that URL. Retrieve webhook secret
[Important] Webhook signatures for test events are generated using the secret key provided in the client-secret-key header of the test event payload.
Follow these steps to verify the signature:
  1. Extract x-timestamp and x-signature from the headers.
  2. Prepare the value_to_digest string by concatenating x-timestamp (as a string) and the raw JSON payload (request body string).
  3. Compute the HMAC using the SHA-256 hash function, with the notification URL’s secret key as the key and value_to_digest as the message.
  4. Compare x-signature from the header with the computed signature. If they match, calculate the difference between the current timestamp and the received timestamp, and check whether it falls within your tolerance window.
If signature verification fails, check these common causes:
  • Use the raw JSON payload: Always use the original, unmodified request body when computing the signature. Do not use a JSON object that has been parsed and re-serialized, as this may alter formatting (whitespace, key order, etc.) and produce a different signature.
  • Verify before parsing: Many JSON libraries automatically format or normalize JSON payloads. Verify the signature before parsing or transforming the body.
  • Check the timestamp format: Use the x-timestamp value exactly as received in the header — no conversion or reformatting.
  • Use the correct secret key: Confirm you are using the secret key associated with the notification URL receiving the webhook. Each webhook subscription has its own unique secret key.
  • Check concatenation order: The value_to_digest string must be constructed in the exact order: x-timestamp (string) followed by the raw JSON payload body.

Code Examples

The following examples show how to verify the signature and return 200 on success or 400 on failure, using Java, PHP, and Node.js.
import org.apache.commons.codec.digest.HmacAlgorithms;
import org.apache.commons.codec.digest.HmacUtils;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.ResponseBody;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

@Controller
public class WebhookExampleController {

  @PostMapping("/webhook/example")
  @ResponseBody
  public String receive(HttpServletRequest request, @RequestBody String payload, HttpServletResponse response) {
    String responseBody = "";

    StringBuilder valueToDigest = new StringBuilder();
    String timestamp = request.getHeader("x-timestamp");
    valueToDigest.append(timestamp);
    valueToDigest.append(payload);

    String signature = request.getHeader("x-signature");
    String secret = getSecret();

    HmacUtils hmacUtils = new HmacUtils(HmacAlgorithms.HMAC_SHA_256, secret);
    if (hmacUtils.hmacHex(valueToDigest.toString()).equals(signature)) {
      // Do something with event
      response.setStatus(HttpServletResponse.SC_OK);
    } else {
      response.setStatus(HttpServletResponse.SC_BAD_REQUEST);
      responseBody = "failed to verify the signature";
    }

    return responseBody;
  }
}
<?php
use RingCentral\Psr7\Response;

function getSecret() {
  return 'your_webhook_secret';
}

function handler($request, $context): Response {
  $timestamp = $request->getHeaderLine('x-timestamp');
  $body = $request->getBody()->getContents();

  $secret = getSecret();
  $signature = $request->getHeaderLine('x-signature');

  if (hash_hmac('sha256', $timestamp . $body, $secret) != $signature) {
    return new Response(400, array(), 'failed to verify the signature');
  }

  // Do something with event
  return new Response(200, array(), $body);
}
// Koa
const crypto = require('crypto');

const secret = '<CLIENT_API_WEBHOOK_SECRET>';

async function webhookController(ctx, next) {
  const { headers } = ctx.request;
  // Use raw body string (e.g. ctx.request.rawBody from middleware); do not use a parsed object.
  const rawBody = ctx.request.rawBody ?? (typeof ctx.request.body === 'string' ? ctx.request.body : JSON.stringify(ctx.request.body));
  const ts = headers['x-timestamp'];
  const valueToDigest = `${ts}${rawBody}`;
  const signatureHex = crypto.createHmac('sha256', secret).update(valueToDigest).digest('hex');

  if (signatureHex === headers['x-signature']) {
    // Do business logic after signature is verified
    return next(); // HTTP response code 200: acknowledge the webhook
  } else {
    ctx.status = 400;
    ctx.body = 'failed to verify webhook signature';
    return;
  }
}

Allowlist IP Addresses

PayLoco sends webhook calls from one of the following IP addresses. You must allowlist these IPs to successfully receive webhook calls: Production
  • 47.76.49.81
  • 43.154.97.39
Sandbox
  • 47.242.161.23
  • 47.76.76.39
For guidance on HTTPS, retries, immediate event acknowledgment, handling duplicate events, and event ordering, see the Webhooks Overview.

Next Steps

Now that you can receive and verify webhooks, you can: