Documentation · Integration Guide
Webhook Integration
This document explains how the ADP Car Market Hub plugin's outgoing webhooks work, how to configure them safely, how a receiving system should validate the payloads, and what to expect operationally. It also covers the limitations of the current implementation so that integrations can be designed around them.
When to use this document
Use this document if you are:
- Connecting the plugin to a CRM, lead routing tool, notification service or workflow automation platform.
- Building or operating the receiving endpoint that the plugin should call.
- Reviewing the security posture of a webhook integration.
- Troubleshooting a receiver that does not appear to be called or that is rejecting payloads.
The audience is an integrator or developer working on the receiving system, together with the WordPress administrator who configures the plugin.
Overview
A webhook is an outgoing HTTP request that the plugin sends to a URL of your choice when a specific event happens on the WordPress site. The plugin posts a JSON document describing the event; the receiving system processes it however it likes (file a CRM ticket, post a chat message, append to a spreadsheet, route a lead, and so on).
The plugin currently exposes two events:
new_lead— fired when a visitor submits the contact form on a vehicle page and a lead is saved.new_import— fired when a vehicle is imported or updated by the importer.
Current behavior note: In the current plugin version only
new_importis actually sent. Thenew_leadpath is wired up (target URL, secret and signing all work) but its trigger is not active in the current code, so a configurednew_leadURL will not receive requests yet. Use the built-in lead emails for lead notifications in the meantime, and configurenew_leadnow if you want the receiver ready for when it is activated.
Each event has its own configurable target URL. A single shared secret can be configured to sign every payload with HMAC-SHA256, so the receiver can verify that the request really came from the plugin.
Webhooks are optional. If a target URL is not configured, no request is sent for that event.
Prerequisites
Before configuring webhooks, prepare:
- A receiving endpoint that accepts an HTTPS
POSTrequest with a JSON body. HTTPS is strongly recommended; an HTTP endpoint exposes the payload in transit. - Knowledge of how the receiver responds to errors, so the retry behaviour described below makes sense for your setup.
- A shared secret value that you can configure both in the plugin and in the receiver (only required if you want signature verification — strongly recommended).
- WordPress administrator access on the site that runs the plugin.
Step by step instructions
- Decide which events to forward. You can configure either or both of
new_leadandnew_import. Leave a target URL empty to disable that event. - Choose a shared secret. Use a long, random string (for example a generated password from your password manager). Keep it confidential — it must never be published.
- Open the plugin settings. Navigate to Car Market Hub → Leads. The Webhooks section on the Leads tab contains the New lead webhook URL, the New import webhook URL and the Webhook secret fields. Verify the exact placement in the current plugin version, as UI grouping can evolve between releases.
- Configure the URLs. Paste the receiving URLs into the corresponding fields. The plugin validates that the value is a syntactically correct URL and skips delivery if it is missing or invalid.
- Configure the secret. Paste the shared secret into the Webhook secret field. Configure the same value in the receiver.
- Save and trigger a test. Save the settings, then trigger a real event:
- For
new_lead: submit the lead form on a vehicle detail page from a private window. - Fornew_import: run the importer (manually or wait for the next scheduled run) so that at least one vehicle is inserted or updated. - Confirm reception. Check the receiver's logs to confirm the request arrived and the signature verified successfully. If something is wrong, see Troubleshooting below.
Configuration reference
| Setting | Purpose |
|---|---|
| New lead webhook URL | Endpoint called when a contact form submission is saved as a lead. |
| New import webhook URL | Endpoint called when a vehicle is imported or updated by the importer. |
| Webhook secret | Shared secret used to sign each payload with HMAC-SHA256. When empty, no signature header is sent. |
The corresponding option keys used internally by the plugin are documented in Option Keys and Settings Storage and Webhook Event Reference.
Request format
The plugin sends each event as an HTTPS POST request with a JSON body and the following headers:
Content-Type: application/jsonX-AS24CI-Signature: <hex>— present only when a webhook secret is configured. The value is the HMAC-SHA256 of the raw request body, using the configured secret as the key, encoded as a lowercase hexadecimal string.
The exact field set in the JSON body may evolve between plugin versions. The reference for the current version is Webhook Event Reference. Both events include at minimum:
event— the event name (new_leadornew_import).timestamp— an ISO-8601 timestamp in UTC of when the event was prepared.- An identifier for the related WordPress object (lead ID for
new_lead, post ID and listing ID fornew_import). - A
dataobject with a small set of summary fields about the event (basic lead fields fornew_lead; basic vehicle fields fornew_import).
Receivers should treat unknown fields as forward-compatible additions and ignore them rather than rejecting the payload. Verify the current field set against the reference document before publishing your integration.
Validating incoming requests
When a webhook secret is configured, the receiver should always verify the signature before trusting the payload. The verification rule is:
- Read the raw request body before any JSON parsing (parsing-and-re-serialising can change byte order or whitespace and break the signature).
- Compute
HMAC-SHA256(raw_body, shared_secret)and encode the result as a lowercase hexadecimal string. - Compare it to the value of the
X-AS24CI-Signatureheader using a constant-time comparison (most languages provide this ashash_equals,crypto.timingSafeEqual,hmac.compare_digestor similar). - Reject the request with
401if the signature is missing or does not match.
Additional defensive checks the receiver should consider:
- Reject HTTP. Only accept HTTPS requests on the receiver.
- Allow-list the source IP of the WordPress site at the network or application layer, in addition to the signature check.
- Idempotency. The same logical event may be delivered more than once (see Delivery behaviour below). Use the lead ID, the post ID, the listing ID or a hash of the payload to deduplicate on the receiver side.
- Timestamp window. Reject requests whose
timestampis far in the past or future to limit the value of replayed payloads. A few minutes of clock skew on either side is normal. - Schema tolerance. Treat extra fields as forward-compatible and unknown event names as something to log rather than crash on.
- Body size limit. Cap the maximum body size at the receiver to protect against malformed input.
If no secret is configured, the receiver has no way to verify the request really came from the plugin; configure a secret unless the network path is fully private and trusted.
Delivery behaviour
The current implementation has the following delivery characteristics. Verify these behaviours in the current plugin version before relying on them.
- First attempt is fire-and-forget. When an event is fired, the plugin sends the first delivery as a non-blocking HTTP
POST. It does not wait for the receiver's response and does not interpret the response code on this attempt. - Scheduled retry attempts. A short time after the first attempt, a follow-up delivery is scheduled through WordPress's task queue. If that delivery returns a
5xxresponse or a connection error, the plugin schedules further retries with a short increasing delay between them. - Retry cap. The plugin does not retry indefinitely. After a small number of unsuccessful attempts the event is dropped silently and is not delivered later.
- Per-event independence. A delivery failure for one event does not affect the next event.
- Order is not guaranteed. Two events fired close together may arrive in either order. Receivers should rely on identifiers and timestamps in the payload, not on arrival order.
- Single-receiver model. Each event has exactly one configured URL. To fan out to several systems, configure a small forwarder (for example an automation platform such as Zapier, Make, n8n, or a self-hosted relay) as the receiver and let it dispatch to the downstream systems.
- Cron dependency for retries. Retries are scheduled through WordPress's task queue. On installations that use Server cron mode (see Server Cron Setup), make sure the companion job that calls
wp-cron.phpis in place — otherwise scheduled retries do not run.
Operational notes
- Logging on the WordPress side. The plugin logs API and import activity to
wp-content/uploads/as24ci-logs/. Webhook delivery is part of normal operation and may not generate verbose logs by default. The most reliable place to see what was delivered is the receiver's own log. - Logging on the receiver side. Log every received request, including the headers (with the signature) and the raw body, before any processing. This makes both incident response and debugging much easier. Apply the receiver's normal data-protection rules — leads contain personal data.
- Personal data. The
new_leadevent contains personal data submitted by the visitor (such as name, email address, phone, message). Treat the receiver as a data processor for this information and apply your normal privacy rules. See Lead Data and Consent and GDPR / DSGVO Notes. - Rotating the secret. When you change the webhook secret in the plugin, update the receiver at the same time. Until both ends agree, every delivery will be rejected by the signature check.
- Changing the URL. Updating the URL takes effect for the next event. There is no built-in mechanism to redeliver historical events.
- Disabling a webhook. Clear the URL field and save. No further events of that type will be sent.
Limitations
- Only the two events described above are emitted. There is no built-in event for vehicle deletion, lead status change, search alert subscription, test drive booking or other plugin actions.
- There is no admin UI for inspecting individual delivery attempts or for replaying historical events.
- There is no native fan-out to multiple receivers per event.
- There is no incoming-webhook endpoint — the plugin only sends webhooks; it does not accept arbitrary inbound webhooks.
- Retry counts and delays may change between plugin versions. Verify the current behaviour against Webhook Event Reference before publishing customer-facing documentation.
Troubleshooting
| Symptom | Likely cause | What to check |
|---|---|---|
| Receiver never sees a request. | The webhook URL is empty, malformed, or unreachable from the WordPress site (DNS, firewall, outbound HTTPS blocked). | Re-check the URL on the Leads tab. Confirm outbound HTTPS works from the WordPress host (see API, Network and SSL Requirements). |
| Receiver sees the request but the signature does not match. | The receiver is verifying the parsed body instead of the raw bytes, the secret on the two sides differs, or the comparison is not constant-time. | Verify the raw body is read before JSON parsing, that the secret matches exactly (no whitespace, no line breaks) and that the comparison uses a constant-time function. |
Receiver sees the request but X-AS24CI-Signature is missing. | The webhook secret is empty in the plugin settings. | Set a secret, save, and re-test. |
| Lead form submissions arrive at the receiver but the same lead is delivered several times. | Expected: the plugin attempts retries when a previous delivery looked unsuccessful, and the first attempt is fire-and-forget. | Make the receiver idempotent using lead_id (or another stable identifier from the payload). |
new_import events do not arrive even though imports run. | The new-import webhook URL is empty or misconfigured. | Re-check the URL on the Leads tab; verify with a manual import. |
| Retries do not seem to happen. | WP-Cron is disabled and no companion wp-cron.php cron job is in place. | See Server Cron Setup and add the companion job. |
Receiver returns 5xx repeatedly during incidents. | Normal failure mode. The plugin will retry a small number of times and then give up for that event. | Make the receiver tolerant or buffer at the receiver side. There is no built-in admin replay. |
| Webhook secret value appears in a log or screenshot. | Insufficient redaction. | Rotate the secret immediately, update the plugin and the receiver, and re-test. |