Preamble

Build an understanding of the event-based communication standards.

Events

Voxie has standardized on CloudEvents as the method of defining data across event-driven communication. We see this as a strong way forward towards interoperability with other cloud providers and tools including Azure and Google Cloud who provide native support.

You can find more information regarding the techincal spec for CloudEvents in the cloudevents/spec repository on Github. Our CloudEvents will always have the specversion, id, source, type, datacontenttype, dataschema, time, and data attributes set. Moreover, datacontenttype will always be set to application/json and dataschema to a URL for the JSON Schema describing the data field. We are compatible with all JSON Schema versions from draft 4 upwards. Finally, note that the id field can be used to uniquely identify any event - a stronger property than required by the CloudEvent specificaition which only mandates uniqueness within each producer.

Delivery

Events matching the filters for a subscription are delivered to the sink via HTTP POST requests. We require sinks to use the https scheme and support HTTP 1.1 or 2 with TLS 1.2 or 1.3. We deliver our events using the structured content mode.

Each delivery is signed according to RFC 9421 using the ecdsa-p256-sha256 algorithm and includes a SHA-256 Content-Digest formatted according to RFC 9530. The signature is labelled sig1, tagged voxie-hooks, and valid for five minutes from its created timestamp. It covers @authority, @method, @path, @query, content-digest, content-length, content-type, date, and user-agent. The keyid signature parameter is an HTTPS URL for the P-256 public signing key. An example delivery HTTP request, with the signature bytes stubbed out, is provided below:

POST /your-endpoint HTTP/1.1
Host: example.com
Content-Type: application/cloudevents+json; charset=UTF-8
Content-Length: 585
Date: Tue, 03 Aug 2021 15:21:35 GMT
Content-Digest: sha-256=:NJTSTlMmExiiFPTegW95oQPfxKnV+kxeOE6PQpU+PTs=:
User-Agent: Voxie/2.0
Signature-Input: sig1=("@authority" "@method" "@path" "@query" "content-digest" "content-length" "content-type" "date" "user-agent");alg="ecdsa-p256-sha256";created=1628004095;expires=1628004395;keyid="https://registry.voxie.com/keys/outbound-hooks/eabdc48d.pem";nonce="27fde337ab04852f23c8eccf3bc4e8a4";tag="voxie-hooks"
Signature: sig1=:...:

{
  "specversion": "1.0",
  "id": "1wDreVpnbNWFKJtyLfvGENtUzqk",
  "source": "voxie/core",
  "type": "com.voxie.contact.created",
  "datacontenttype": "application/json",
  "dataschema": "https://registry.voxie.com/schema/v1/contact/created.json",
  "time": "2021-08-03T15:21:35Z",
  "comvoxiepublic": true,
  "comvoxieteam": 1,
  "data": {
    "at": "2021-08-03T15:21:35Z",
    "team_id": 1,
    "contact_id": 288331,
    "contact_phone_number": "+17092080726",
    "contact_email": "[email protected]",
    "contact_first_name": "Example",
    "contact_last_name": "Contact"
  }
}

Verifying deliveries

Verify the signature against the request as it was received, before parsing or re-encoding the JSON body. Use an RFC 9421-compatible implementation to parse Signature-Input and Signature, select sig1, require the documented algorithm and tag, and check that the current time falls between created and expires, allowing only the clock skew your application requires. The decoded signature is the 64-byte concatenation of the 32-byte, big-endian r and s values required by RFC 9421, not an ASN.1 DER value.

Resolve keyid only after confirming that its scheme is https, its host is exactly registry.voxie.com, and its path starts with /keys/outbound-hooks/; do not trust arbitrary key URLs supplied in a request. The key may be cached by URL.

After verifying the signature, independently calculate the SHA-256 digest of the exact request-body bytes and compare it with Content-Digest according to RFC 9530. Signature verification alone proves that the Content-Digest header was signed, not that the body matches that digest. Reverse proxies and middleware must preserve the covered request components until verification is complete.

Note that events will be delivered using "at least once" semantics and as close to when they were raised as possible. This means that events may be delivered out of order or multiple times. CloudEvents can be identified by their id field. Subscribers can identify duplicates using the id attribute. Failed deliveries will be re-tried for up to 24 hours.