Skip to content

Webhook introduction and configuration

Updated: 2026-09-16 21:49

The platform sends signed final order-status events as UTF-8 JSON to the HTTPS Webhook endpoints configured independently in Merchant Portal. An endpoint is not bound to an API Key and is not sent on each Payment or Payout creation request. Webhook requests carry Content-Type: application/json. This is an outbound Webhook delivery from the platform, not an ordinary merchant API endpoint.

Configuration and secret lifecycle

  1. Create one or more endpoints and select the event types for each endpoint. One event type can be delivered to multiple endpoints. New endpoints are disabled until explicitly enabled.
  2. On endpoint creation, the platform generates a Webhook Secret and Webhook Secret ID and displays the current Secret for 60 seconds. An authorized merchant administrator can reveal that current version again only after a fresh Google verification; historical, revoked, archived or operations-suspended versions cannot be revealed.
  3. Store the Secret in a server-side secret manager and deploy a receiver that verifies the raw Body before accepting events.
  4. Enable delivery only when the receiver can durably accept events. Return HTTP 200 OK in receiver examples; the platform treats any 2xx response as successful receipt.
  5. Changing the URL disables the endpoint and skips pending deliveries until the new receiver is reviewed and the endpoint is enabled again. Disable only this endpoint before rotating its Secret; API Key status is unrelated.
  6. Initial delivery and automatic retries use the URL and Secret version saved when that delivery record was created. A merchant or operations manual replay uses the endpoint's current URL and current Secret. Retain old Secret-ID mappings through the automatic retry window and select them strictly by Webhook-Secret-Id.

Merchant Portal shows successful, failed and retrying deliveries and permits an authorized single-delivery retry. The platform also retries eligible failures automatically.

Request headers

These headers are notification-specific. They are sent only when the platform calls the merchant Webhook endpoint and are separate from the headers merchants send when calling ordinary APIs.

HeaderExampleDescription
Content-Typeapplication/jsonIndicates that this Webhook Body is UTF-8 JSON. It is not included in the Webhook verification payload.
Request-Idreq_fixture_000000000000000000000001Tracking ID for this delivery request.
Webhook-Secret-Idwhsecid_fixture_001Webhook Secret identifier selected for this delivery. Use it to choose the locally stored Webhook Secret before verifying Signature. Merchant Portal shows this ID when the Secret is generated or rotated.
Timestamp1780272000Unix delivery timestamp text in seconds. A retry may use a new timestamp.
Noncenonce_fixture_webhook_001Delivery random string. A retry may use a new value.
Signaturev1=<64 lowercase hex>Versioned HMAC-SHA256 signature of the timestamp, random string and exact raw body.

Header names are case-insensitive. The documentation shows canonical unprefixed Header names; do not add product, company or brand prefixes.

HTTP example

{merchantWebhookUrl} is the complete HTTPS notification receiving address configured by the merchant in the merchant portal. The domain and path are both defined by the merchant. Payment and Payout notifications can share one endpoint and be distinguished by Body eventType, or be routed to different endpoints by selecting different event subscriptions. The same event type may be subscribed by multiple endpoints.

Example merchant URL: https://merchant.example.com/.

http
POST {merchantWebhookUrl}
Content-Type: application/json
Request-Id: req_fixture_000000000000000000000001
Webhook-Secret-Id: whsecid_fixture_001
Timestamp: 1780272000
Nonce: nonce_fixture_webhook_001
Signature: v1=<64 lowercase hex>

{
  "eventId": "evt_fixture_001",
  "eventType": "PAYMENT_SUCCEEDED",
  "merchantNo": "MCH_FIXTURE_001",
  "order": {
    "platOrderNo": "PHI112606010000000001001",
    "merchantOrderNo": "DEMO_PAY_202606010001",
    "status": "SUCCEEDED",
    "productCode": "PH_PHP_PAYMENT_QRPH_GCASH",
    "country": "PH",
    "amount": {
      "value": "100.50",
      "currency": "PHP"
    },
    "referenceNo": "REF-20260601-0001",
    "customData": {
      "cartId": "CART-10001",
      "customerRef": "CUST-90001"
    },
    "createTime": 1780272000,
    "updateTime": 1780275600
  }
}

Signature verification

The signing payload has exactly three lines. Append one newline byte \n after every line, including the last line:

text
signingPayload = timestamp + "\n" + nonce + "\n" + rawBody + "\n"
signature = "v1=" + lowercase_hex(HMAC-SHA256(webhookSecret, signingPayload))

Capture the raw body before JSON parsing, formatting or logging transformations. Check the timestamp against your configured policy, then select the exact locally stored Secret by Webhook-Secret-Id. Treat the Header as an untrusted selector into a local allowlist. Reject processing if the Secret ID is missing or unknown, and never try every historical Secret as a fallback.

Compare the expected and received signatures in constant time. Parse and process the JSON only after timestamp and signature verification succeeds.

WebhookEvent fields

FieldTypeRequiredDescription
eventIdstringYesStable event ID used for deduplication.
eventTypestringYesStable final-status event type used to route business handling.
merchantNostringYesMerchant number that owns the notified order.
orderobjectYesFinal order status data carried by this Webhook.
order.platOrderNostringYesActual core platform order number for support and reconciliation. Treat it as opaque and do not infer the order type from its format.
order.merchantOrderNostringYesMerchant-side order number used for business status update.
order.statusstringYesCurrent final order status.
order.failReasonstringNoMerchant-safe failure or cancellation reason. Returned when order.status is FAILED or CANCELED; omitted for SUCCEEDED.
order.productCodestringYesProduct code used by the order.
order.countrystringYesISO 3166-1 alpha-2 country code.
order.amountobjectYesOrder amount object.
order.amount.valuestringYesAmount value: transaction amount in major currency units; precision follows amount.currency and the selected productCode.
order.amount.currencystringYesCurrency code: transaction currency, using ISO 4217 three-letter uppercase code. See supported countries and currencies.
order.referenceNostringNoUpstream payment-network, payment-institution or recipient-institution reference number used to match payer receipts or recipient institution investigation results to the order.
order.customDataobjectNoNon-sensitive merchant-defined key-value pairs saved by the first accepted create request; idempotent duplicates never merge or overwrite them.
order.recipientSummaryobjectNoMasked recipient account summary. Returned for Payout notifications when available.
order.recipientSummary.bankCodestringYes, when recipientSummary is returnedRecipient bank code.
order.recipientSummary.accountNamestringYes, when recipientSummary is returnedRecipient account holder name.
order.recipientSummary.accountNoLast4stringYes, when recipientSummary is returnedLast four characters of the recipient account number.
order.createTimetimestampYesUnix timestamp in seconds, returned as a JSON number, when the order was created.
order.updateTimetimestampYesUnix timestamp in seconds, returned as a JSON number, when the current notified order status became effective. This value is stable across Webhook retries.

Timestamp in the Header is the timestamp of this HTTP delivery and can change on retry. It is sent as Header text. order.updateTime in the Body is a JSON number for the time when the notified order status became effective, and remains stable for the same event.

Body eventType uses this fixed event enum:

eventTypeMeaning
PAYMENT_SUCCEEDEDPayment order reached a successful final state.
PAYMENT_CANCELEDPayment order expired or was closed before success.
PAYOUT_SUCCEEDEDPayout order reached a successful final state.
PAYOUT_FAILEDPayout order reached a failed final state.

order.referenceNo is not the merchant order number, platform order number or channel order number. Use it only for payer receipt matching, recipient institution investigation and support. It is not used for ordinary lookup or idempotency.

Delivery handling

  • Validate the signature, deduplicate by Body eventId, persist the accepted event durably, and then return any HTTP 2xx response as quickly as possible. No business response body is required.
  • The platform treats any 2xx response as successful receipt and stops retrying that delivery. Even if the order was already processed, return 2xx after idempotent handling to stop later retries.
  • Non-2xx responses, receiver timeouts, connection failures or TLS failures trigger automatic retry. The default retry window is 24 hours. Retries are approximately scheduled at 1m, 2m, 4m, 8m, 16m, 32m, and then about every 1h. Actual delivery time may vary and is not guaranteed to the exact second. Events still failing after the retry window enter manual compensation.
  • Deduplicate by Body eventId; Request-Id identifies one delivery request and is used only for troubleshooting. It must not be used as the business deduplication key.
  • Process accepted events asynchronously and keep business handling idempotent. Webhooks do not guarantee strict ordering; query by merchantOrderNo when a local state transition is unclear.
  • A retry may carry a new Request-Id, delivery timestamp text, nonce and signature. Body eventId and order.updateTime remain the original event values.
  • Reject processing when Webhook-Secret-Id, timestamp or signature verification fails. Do not parse or process the JSON payload before verification succeeds.

The machine schema is components.schemas.WebhookEvent in the OpenAPI Schema. See the Payment order webhook reference and Payout order webhook reference for rendered schemas and Java verification examples. Verify independent implementations with the provided Webhook signature test vectors.