PayArc Webhook Receiver
The PayArc webhook receiver accepts incoming dispute event notifications from PayArc and stores them in the rm_webhooks table. A separate cron-driven processor then reads those rows and performs the full dispute-matching pipeline.
Two-Step Architecture
PayArc ──POST──► /wp-json/gsm-middleware/v1/webhooks/payarc
│
▼
rm_webhooks (status = 0, pending)
│
▼ (cron every 10 min)
Dispute_Processor
│
┌────┴────┐
▼ ▼
NMI lookup Signifyd
│
rm_payarc_dispute_linkages
Step 1 — Receive: A public REST endpoint accepts the POST from PayArc, validates JSON, deduplicates by event_id, and inserts a row into rm_webhooks. Responds in < 200 ms.
Step 2 — Process: A cron job (or manual trigger) calls POST /wp-json/gsm-middleware/v1/disputes/process, which reads pending rows from rm_webhooks, matches each dispute to an NMI transaction, creates a linkage record, and submits to Signifyd.
Endpoint Reference
Receive PayArc Webhook
POST /wp-json/gsm-middleware/v1/webhooks/payarc
| Property | Value |
|---|---|
| Authentication | None (public) |
| Content-Type | application/json |
| Source IP | 184.73.45.255 (PayArc) |
Request Body
Raw JSON sent by PayArc. The expected top-level keys are:
| Key | Type | Description |
|---|---|---|
event_type | string | Dispute event name (e.g. dispute.created) |
timestamp | string | ISO-8601 UTC timestamp |
api_response | string | JSON-encoded object containing case_id / case_number |
request_payload | string | JSON-encoded original request that triggered the event |
remote_addr | string | IP of the client that made the original request |
user_agent | string | User-agent of the client |
account_id | string | PayArc merchant account identifier |
Success Response — 202 Accepted
{
"success": true,
"message": "Webhook received.",
"webhook_id": 123
}
Duplicate Response — 200 OK
Returned when the same event_id has already been stored. PayArc will stop retrying.
{
"success": true,
"message": "Webhook already received."
}
Error Responses
| HTTP | Code | Cause |
|---|---|---|
400 | empty_payload | Request body is empty |
400 | invalid_json | Body is not valid JSON |
500 | db_error | Database insert failed |
Security
Why There Is No Signature Verification
PayArc does not provide an HMAC signing secret or any equivalent mechanism. The endpoint can only validate that the incoming body is well-formed JSON.
Recommended Mitigations
-
IP Whitelist — Restrict
POSTrequests to/wp-json/gsm-middleware/v1/webhooks/payarcto the known PayArc source IP at your WAF or Nginx/Apache level.PayArc webhook source IP: 184.73.45.255 -
TLS — Ensure the endpoint is served over HTTPS only.
-
Rate Limiting — Apply per-IP rate limiting to prevent abuse if the IP whitelist cannot be enforced.
Nginx Example (IP Whitelist)
location = /wp-json/gsm-middleware/v1/webhooks/payarc {
allow 184.73.45.255;
deny all;
try_files $uri $uri/ /index.php$is_args$args;
}
Idempotency
The receiver extracts an event_id from each payload and stores it in the event_id column (unique index) of rm_webhooks. If PayArc re-delivers the same event the receiver returns HTTP 200 immediately without inserting a duplicate row.
event_id derivation order:
api_response.case_id→payarc_case_{case_id}api_response.case_number→payarc_case_{case_number}timestamp+event_type→payarc_{unix_ts}_{md5_of_type}null(no deduplication possible)
Database
rm_webhooks Table
Rows written by this endpoint:
| Column | Type | Description |
|---|---|---|
id | INT UNSIGNED | Auto-increment primary key |
received_at | DATETIME | UTC timestamp at time of receipt |
source | VARCHAR(50) | Always payarc for this endpoint |
event_id | VARCHAR(255) | Idempotency key (unique) |
webhook_type | VARCHAR(100) | Value of event_type from the payload |
payload | LONGTEXT | Raw JSON payload |
hash | VARCHAR(64) | SHA-256 of the raw payload |
status | TINYINT | 0 = pending, 1 = processed, 2 = error |
processed_at | DATETIME | Set by Dispute_Processor on completion |
last_error | TEXT | Last error message from Dispute_Processor |
attempt_count | INT UNSIGNED | Incremented on each processing attempt |
The table is created by database migration 006_create_rm_webhooks_table.php, which also runs idempotent ALTER TABLE statements for deployments where the table was previously created by the legacy gsm-integrator plugin.
Logging
The receiver logs to the payarc-webhook-receiver channel via Simple_Logger.
Typical log messages:
[INFO] PayArc webhook received — event_id: payarc_case_12345 | type: dispute.created
[INFO] PayArc webhook stored — rm_webhooks.id: 123 | event_id: payarc_case_12345 | type: dispute.created
[INFO] PayArc webhook duplicate ignored (event_id: payarc_case_12345, existing row: 99).
[WARN] PayArc webhook rejected: invalid JSON payload.
[ERROR] PayArc webhook DB insert failed — event_id: payarc_case_12345 | DB error: ...
Migration from gsm-integrator
The legacy endpoint was a standalone PHP script with no WordPress bootstrap:
POST https://yoursite.com/wp-content/plugins/gsm-integrator/webhook/webhook.php
The new endpoint is a proper WordPress REST API route:
POST https://yoursite.com/wp-json/gsm-middleware/v1/webhooks/payarc
Cutover Checklist
- Deploy gsm-middleware v1.8.0 (includes
PayArc_Webhook_API, migration006) - Verify migration
006ran —rm_webhookstable exists in tasksdb - Update the webhook URL in the PayArc merchant portal to the new endpoint
- Update the IP whitelist rule on your WAF / Nginx to allow
184.73.45.255to the new path - Send a test webhook from PayArc and confirm a row appears in
rm_webhooks - Run
POST /wp-json/gsm-middleware/v1/disputes/processand confirm the row is processed - Decommission
gsm-integrator/webhook/webhook.php(or leave in place temporarily as a fallback)
Related
- PayArc Dispute Processing — Step 2: the processor that reads
rm_webhooksand matches disputes - REST API Reference — Full endpoint listing