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:

HeaderValue
X-VECU-Event-IDThe CloudEvents id field
X-VECU-Event-TypeThe CloudEvents type field
X-VECU-TimestampUnix timestamp of delivery
X-VECU-Retry-CountNumber of previous delivery attempts
X-VECU-Signaturesha256={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 TypeExampleUse 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:

  1. exclude -- if matched, event is skipped
  2. include -- exact match delivers the event
  3. patterns -- wildcard match delivers the event
  4. productGroups -- expanded namespace match delivers the event
  5. 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