Webhooks
Webhooks deliver real-time payment event notifications to your application. Rather than polling the API for status updates, configure a webhook endpoint to receive a push notification whenever a payment status changes β submitted, settled, returned, or held for compliance review.
payment.settled and payment.returned events to update your core banking records when ACH settlement confirmations and return items arrive.
How Webhooks Work
- You register a webhook endpoint URL in the PayPlus Admin Console or via the Webhooks API.
- When a payment event occurs, PayPlus sends an HTTPS
POSTrequest to your endpoint with the event payload in the request body. - Your endpoint responds with HTTP
200to acknowledge receipt. Any other response code (or no response within 30 seconds) triggers the retry sequence. - PayPlus logs all webhook delivery attempts β successful and failed β accessible at Administration > API > Webhook Logs.
eventId field to deduplicate events before processing β store event IDs you've already handled and skip duplicates.
Webhook Registration
Register via Admin Console
Navigate to Administration > API > Webhooks > Add Endpoint. Enter the endpoint URL, select the event types, and select Save. PayPlus generates and displays the signing secret β copy it before leaving this page. Itβs shown only once.
Register via API
Required scope: webhooks:manage
POST /v2/webhooks HTTP/1.1
Authorization: Bearer eyJhbGciOiJSUzI1NiJ9...
Content-Type: application/json
{
"url": "https://core.yourbank.internal/payplus/events",
"events": [
"payment.settled",
"payment.returned",
"compliance.hold",
"compliance.released"
],
"description": "Core banking settlement integration",
"enabled": true
}
201 Created
{
"data": {
"webhookId": "wh_a3b9c1d7e2f4",
"url": "https://core.yourbank.internal/payplus/events",
"events": ["payment.settled", "payment.returned", "compliance.hold", "compliance.released"],
"signingSecret": "whsec_7f3b2c9a1d4e6f8a0b2c4d6e8f0a2b4c",
"enabled": true,
"createdAt": "2026-03-15T09:00:00Z"
}
}
signingSecret is returned only once in this response. Store it in your application's secrets management system immediately. If you lose the signing secret, you must delete and re-create the webhook registration.
Manage Webhooks
List all registered webhook endpoints for this API client.
Get a specific webhook registration. Does not return the signing secret.
Delete a webhook endpoint. PayPlus will stop sending events to this URL immediately.
Signature Verification
Every webhook request includes an X-PayPlus-Signature header containing an HMAC-SHA256 signature. Always verify this signature before processing the event β it proves the request came from PayPlus and that the payload has not been tampered with in transit.
Verification Algorithm
# Python example
import hmac
import hashlib
def verify_signature(payload_body: bytes, signature_header: str, signing_secret: str) -> bool:
# signature_header format: "t=1742038800,v1=abc123..."
parts = dict(part.split("=", 1) for part in signature_header.split(","))
timestamp = parts["t"]
received_sig = parts["v1"]
# Construct the signed payload: timestamp + "." + raw body
signed_payload = f"{timestamp}.".encode() + payload_body
# Compute HMAC-SHA256
expected_sig = hmac.new(
signing_secret.encode(),
signed_payload,
hashlib.sha256
).hexdigest()
# Constant-time comparison to prevent timing attacks
if not hmac.compare_digest(expected_sig, received_sig):
return False # Signature mismatch β reject the request
# Optional: reject events older than 5 minutes (replay protection)
import time
if abs(time.time() - int(timestamp)) > 300:
return False
return True
Event Reference
| Event Type | Description | Applies To |
|---|---|---|
payment.validated | Payment passed format validation and entered the workflow | All rails |
payment.approved | Payment approved (by workflow rule or human approver); queued for submission | All rails |
payment.submitted | Payment submitted to the payment network | All rails |
payment.settled | Payment settled β funds have moved. For ACH: settlement date passed and FedACH confirmation received. For Fedwire/RTP/FedNow: real-time settlement confirmation received. | All rails |
payment.returned | Payment returned by the receiving bank after settlement. Includes return code and reason. | ACH, RTP, FedNow |
payment.rejected | Payment rejected by PayPlus validation, compliance review, approval, or the payment network | All rails |
compliance.hold | Payment placed on OFAC compliance hold pending Compliance Officer review | All rails |
compliance.released | Compliance hold released β payment cleared to continue processing | All rails |
compliance.rejected | Payment rejected by Compliance Officer from the hold queue | All rails |
wire.recall.resolved | Wire recall request has been accepted (funds returned) or rejected by the beneficiary bank | Fedwire, SWIFT |
rfp.fulfilled | Request for Payment has been fulfilled β the payer approved the RfP and payment settled | RTP, FedNow |
rfp.declined | Payer declined the Request for Payment | RTP, FedNow |
batch.completed | ACH batch processing completed β includes counts of valid, invalid, submitted, and returned records | ACH |
Retry Logic
If your endpoint does not return HTTP 200 within 30 seconds, PayPlus retries the delivery using an exponential backoff schedule:
| Attempt | Delay After Previous Attempt |
|---|---|
| 1 (initial) | β |
| 2 | 1 minute |
| 3 | 5 minutes |
| 4 | 30 minutes |
| 5 | 2 hours |
| 6 (final) | 6 hours |
After 6 failed attempts, PayPlus marks the delivery as failed and sends an alert to the System Administrator. Events are retained for 72 hours for manual retry via the Admin Console.
Payload Examples
payment.settled β ACH
{
"eventId": "evt_7f3c1d9e2a4b",
"eventType": "payment.settled",
"timestamp": "2026-03-17T12:00:00Z",
"data": {
"paymentId": "pmt_ach_a3k9f2m8x7p4r1q0",
"rail": "ach",
"status": "settled",
"amount": 350000,
"effectiveDate": "2026-03-17",
"traceNumber": "021000021000001",
"referenceId": "VENDOR-PMT-20260315-001",
"settledAt": "2026-03-17T12:00:00Z"
}
}
payment.returned β ACH R01
{
"eventId": "evt_9a1c3e5f7b2d",
"eventType": "payment.returned",
"timestamp": "2026-03-18T08:30:00Z",
"data": {
"paymentId": "pmt_ach_a3k9f2m8x7p4r1q0",
"rail": "ach",
"status": "returned",
"returnCode": "R01",
"returnReason": "Insufficient Funds",
"returnSettlementDate": "2026-03-18",
"representmentEligible": true,
"representmentDeadline": "2026-09-14",
"referenceId": "VENDOR-PMT-20260315-001"
}
}
compliance.hold β OFAC Screening
{
"eventId": "evt_b2d4f6a8c0e1",
"eventType": "compliance.hold",
"timestamp": "2026-03-15T14:35:12Z",
"data": {
"paymentId": "pmt_wire_f3a1b9c7d2e4",
"rail": "swift",
"holdReason": "OFAC_POSSIBLE_MATCH",
"matchedField": "beneficiary.name",
"matchScore": 88,
"referenceId": "CLOSING-2026-03-15-001",
"heldAt": "2026-03-15T14:35:12Z"
}
}
payment.settled β FedNow (real-time)
{
"eventId": "evt_d3f5a7c9e1b2",
"eventType": "payment.settled",
"timestamp": "2026-03-15T14:30:07Z",
"data": {
"paymentId": "pmt_inst_b9c3d1f7e2a4",
"rail": "fednow",
"status": "settled",
"amount": 50000,
"uetr": "4a7f2b9c-3d1e-5f6a-8b9c-0d1e2f3a4b5c",
"settlementTimestamp": "2026-03-15T14:30:07Z",
"processingTimeMs": 5823,
"referenceId": "RENT-MAR2026-JD"
}
}