Webhook Setup
Start receiving VECU platform events in under 10 minutes. This guide walks through registering a webhook endpoint, verifying HMAC signatures, and testing delivery.
Prerequisites
- An OAuth2 access token (see Authentication)
- A publicly accessible HTTPS endpoint that accepts POST requests
- The endpoint must respond within 30 seconds and return a 2xx status code
Step 1: Register Your Endpoint
Create a webhook subscription with the events you want to receive. Use product groups for broad subscriptions or individual event types for precision.
curl -X POST https://{cip_vecu_config_url} /event-relay/subscriptions \
-H "Authorization: Bearer YOUR_ACCESS_TOKEN" \
-H "Content-Type: application/json" \
-d "{
\"url\": \"https://your-app.com/webhooks/vecu\",
\"authType\": \"HMAC_SHA256\",
\"authConfig\": {
\"secret\": \"$VECU_WEBHOOK_SECRET\"
},
\"eventFilters\": {
\"productGroups\": [\"CUSTODY_SDK\"]
}
}"
Step 2: Save Your HMAC Secret
The response includes authConfig.secret -- this is your HMAC signing key. It is only returned once at creation time. Store it securely (e.g., in your secrets manager).
{
"id": "01JA4KXYZ...",
"subscriberId": "your-client-id",
"url": "https://your-app.com/webhooks/vecu",
"authType": "HMAC_SHA256",
"authConfig": {
"secret": "your-webhook-secret-min-32-characters-long",
"secretArn": "arn:aws:secretsmanager:us-east-1:..."
},
"status": "active"
}
If you lose the HMAC secret, you will need to delete the webhook and create a new one.
Step 3: Handle Incoming Events
Your endpoint receives events as HTTP POST requests with a CloudEvents 1.0 JSON body. Verify the HMAC signature before processing.
Event Payload
Every webhook POST contains a CloudEvents 1.0 envelope:
{
"id": "01JA4KXYZ...",
"source": "vecu.custody-service",
"type": "custody.vehicle.released",
"specversion": "1.0",
"time": "2025-01-15T14:30:00Z",
"datacontenttype": "application/json",
"data": {
"_platform": {
"correlationId": "corr_abc123",
"actor": { "actorId": "driver_456", "actorType": "user" }
},
"vin": "5XXXX00000XEXMPL0"
}
}
Delivery Headers
Every webhook POST includes these headers:
| Header | Value |
|---|---|
X-VECU-Event-ID | The CloudEvents id field |
X-VECU-Event-Type | The CloudEvents type field |
X-VECU-Timestamp | Unix timestamp of delivery |
X-VECU-Retry-Count | Number of previous delivery attempts |
X-VECU-Signature | sha256={hmac} (when authType=HMAC_SHA256) |
Signature Verification
The HMAC-SHA256 signature is computed over {X-VECU-Timestamp}.{request_body_utf8} using your secret as the key. The result is delivered as:
X-VECU-Signature: sha256=5d7a9b2c4d6e8f1a3b5c7d9e2f4a6b8c...
Strip the sha256= prefix before comparing against your computed digest.
import hashlib
import hmac
import os
from flask import Flask, request, jsonify
app = Flask('vecu-webhook')
WEBHOOK_SECRET = os.environ["VECU_WEBHOOK_SECRET"] # Set in your environment
@app.route("/webhooks/vecu", methods=["POST"])
def handle_webhook(): # Verify HMAC signature
timestamp = request.headers.get("X-VECU-Timestamp", "")
raw_signature = request.headers.get("X-VECU-Signature", "")
signature = raw_signature.removeprefix("sha256=")
message = f"{timestamp}.{request.data.decode('utf-8')}".encode()
expected = hmac.new(
WEBHOOK_SECRET.encode(),
message,
hashlib.sha256,
).hexdigest()
if not hmac.compare_digest(signature, expected):
return jsonify({"error": "Invalid signature"}), 401
event = request.json
event_type = event["type"] # CloudEvents field
data = event["data"] # Event-specific fields (platform metadata under data["_platform"])
# Route by event type
if event_type == "custody.vehicle.released":
handle_vehicle_released(data)
elif event_type == "credential.revoked":
handle_credential_revoked(data)
return jsonify({"received": True}), 200
Use the id field (CloudEvents) for deduplication. The relay service provides
at-least-once delivery, so your endpoint may receive the same event more than
once. You can also use X-VECU-Event-ID from the request headers.
Filter Options
You can filter events using several strategies:
| Filter Type | Example | Use When |
|---|---|---|
| Product groups | "productGroups": ["CUSTODY_SDK"] | You want all events for an SDK integration |
| Wildcard patterns | "patterns": ["custody.*"] | You want all events from a service prefix |
| Exact event types | "include": ["custody.vehicle.released"] | You want specific events only |
| Exclusions | "exclude": ["credential.expired"] | You want everything except certain events |
Filter evaluation order:
exclude-- if matched, event is skippedinclude-- exact match delivers the eventpatterns-- wildcard match delivers the eventproductGroups-- expanded namespace match delivers the event- All lists empty -- catch-all, delivers everything
Retry Behavior
Failed deliveries are retried automatically:
- 6 attempts with exponential backoff
- HTTP 5xx and 429 responses trigger retries
- HTTP 4xx responses (except 429) are not retried
- Events that exhaust all retries are sent to a dead letter queue (DLQ)
Dead Letter Queue (DLQ) handling: Events that exhaust all 6 retry attempts
are moved to a dead letter queue. To detect delivery failures, monitor the
X-VECU-Retry-Count header in incoming requests — a value of 5 means the
next failure will move the event to the DLQ.
For manual replay of DLQ events, contact VECU platform support at vecu-support@coxautoinc.com with your webhook ID and the approximate time range of failed events. The team can replay events within 30 days of original delivery. After 30 days, events are permanently purged from the DLQ.
Next Steps
- Event Envelope -- Understand the full event structure
- Custody SDK Events -- Browse custody and credential events
- IDV SDK Events -- Browse identity verification events
- Webhook API Reference -- Full API documentation