Skip to main content

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
PropertyValue
AuthenticationNone (public)
Content-Typeapplication/json
Source IP184.73.45.255 (PayArc)

Request Body

Raw JSON sent by PayArc. The expected top-level keys are:

KeyTypeDescription
event_typestringDispute event name (e.g. dispute.created)
timestampstringISO-8601 UTC timestamp
api_responsestringJSON-encoded object containing case_id / case_number
request_payloadstringJSON-encoded original request that triggered the event
remote_addrstringIP of the client that made the original request
user_agentstringUser-agent of the client
account_idstringPayArc 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

HTTPCodeCause
400empty_payloadRequest body is empty
400invalid_jsonBody is not valid JSON
500db_errorDatabase 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.

  1. IP Whitelist — Restrict POST requests to /wp-json/gsm-middleware/v1/webhooks/payarc to the known PayArc source IP at your WAF or Nginx/Apache level.

    PayArc webhook source IP: 184.73.45.255
  2. TLS — Ensure the endpoint is served over HTTPS only.

  3. 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:

  1. api_response.case_idpayarc_case_{case_id}
  2. api_response.case_numberpayarc_case_{case_number}
  3. timestamp + event_typepayarc_{unix_ts}_{md5_of_type}
  4. null (no deduplication possible)

Database

rm_webhooks Table

Rows written by this endpoint:

ColumnTypeDescription
idINT UNSIGNEDAuto-increment primary key
received_atDATETIMEUTC timestamp at time of receipt
sourceVARCHAR(50)Always payarc for this endpoint
event_idVARCHAR(255)Idempotency key (unique)
webhook_typeVARCHAR(100)Value of event_type from the payload
payloadLONGTEXTRaw JSON payload
hashVARCHAR(64)SHA-256 of the raw payload
statusTINYINT0 = pending, 1 = processed, 2 = error
processed_atDATETIMESet by Dispute_Processor on completion
last_errorTEXTLast error message from Dispute_Processor
attempt_countINT UNSIGNEDIncremented 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, migration 006)
  • Verify migration 006 ran — rm_webhooks table 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.255 to 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/process and confirm the row is processed
  • Decommission gsm-integrator/webhook/webhook.php (or leave in place temporarily as a fallback)