Webhooks Overview
Describes how the Spark platform delivers outbound webhook events to third-party systems when resources change in an upstream MLS.
- Overview
- Event Types
- Payload Format
- Verifying Authenticity
- Request Headers
- Delivery, Retries, and Timeouts
- Filtering
- Catch-up Replay
- Getting Subscribed
- Quick Reference
Overview
When a resource changes in an upstream MLS (a listing, a member/agent record, or an office
record), the Spark platform emits an entity event. For every webhook
subscription that matches the event's MLS and resource type, an HTTP POST
request is delivered to your configured URL containing a RESO-formatted payload.
Events follow the RESO Web API Entity Event convention, so the payload shape will be familiar if you already consume RESO data.
| Transport | HTTPS POST |
| Content-Type | application/json |
| Authenticity | HMAC-SHA256 signature in the Signature header |
| Retries | Up to 3 attempts with exponential backoff |
| Timeout | Your endpoint must respond within ~2 seconds |
| Success | Any 2xx response |
| User-Agent | NotificationSystem/0.1 |
Event Types
Every event describes a change to one of three resource types:
| Resource Type | ResourceName Value |
Description |
|---|---|---|
| Property | Property |
A listing was created or modified |
| Member | Member |
A member/agent record was created or modified |
| Office | Office |
An office record was created or modified |
A single subscription can receive any combination of these. Which events you receive is controlled by resource-type and MLS filters on your subscription (see Filtering).
Payload Format
Each delivery is a JSON document containing a RESO entity-event envelope. The top-level
object carries a @reso.context marker and a value array. Note
that value can contain multiple events within the same POST body — each of
them should be consumed and processed by your system. When a single event is delivered,
value still uses the bulk delivery format even if it only contains a single entry.
{
"@reso.context": "urn:reso:metadata:2.0:resource:entityevent",
"value": [
{
"EntityEventSourceSystemInternalID": "123",
"EntityEventTimestamp": "2026-06-10T14:32:05+00:00",
"ResourceName": "Property",
"ResourceRecordKey": "20260514111441584701000000",
"ResourceRecordModificationTimestamp": "2026-06-10T14:31:58+00:00",
"SystemID": "M1000123456"
}
]
}
Event Object Fields
| Field | Type | Always Present | Description |
|---|---|---|---|
ResourceName |
string | yes | Resource type: Property, Member, or Office. |
ResourceRecordKey |
string | yes | The unique key of the changed record in the source system. Use this to fetch the full record. |
EntityEventTimestamp |
string (ISO 8601) | yes | When the event was generated. |
EntityEventSourceSystemInternalID |
string | yes | Internal identifier of the source MLS/system that produced the event. |
SystemID |
string | null | yes | RESO OUID value. |
ResourceRecordModificationTimestamp |
string (ISO 8601) | no | When the underlying record was last modified. Included only when known. |
Forward Compatibility
Event objects may carry extra fields beyond those listed above, and keys are serialized in alphabetical order. Treat the payload as forward-compatible: ignore fields you do not recognize rather than rejecting the payload, and do not depend on key ordering.
What a Webhook Does Not Contain
Events are notifications of change, not full data records. They tell you
what changed (ResourceRecordKey) and when. To obtain the
current state of the record, fetch it from the source system or data API using the
ResourceRecordKey.
Verifying Authenticity
Every request includes a Signature header so you can confirm it came from the
Spark platform and was not tampered with in transit.
- Header:
Signature - Algorithm: HMAC-SHA256
- Secret: the shared secret assigned to your subscription
- Signed content: the raw JSON request body, exactly as received (the full envelope, including
@reso.contextandvalue) - Encoding: lowercase hexadecimal digest
To verify, compute the HMAC-SHA256 of the raw request body bytes using your
shared secret and compare it to the Signature header using a constant-time
comparison.
Use the Raw Body
Compute the signature against the raw body before any JSON parsing or re-serialization. Re-encoding the JSON can change whitespace or key order and will produce a different digest.
Example: PHP
$payload = file_get_contents('php://input');
$signature = $_SERVER['HTTP_SIGNATURE'] ?? '';
$expected = hash_hmac('sha256', $payload, $yourSecret);
if (! hash_equals($expected, $signature)) {
http_response_code(401);
exit;
}
Example: Node.js
const crypto = require('crypto');
function verify(rawBody, signatureHeader, secret) {
const expected = crypto
.createHmac('sha256', secret)
.update(rawBody) // rawBody must be the exact bytes received
.digest('hex');
const a = Buffer.from(expected);
const b = Buffer.from(signatureHeader || '');
return a.length === b.length && crypto.timingSafeEqual(a, b);
}
Example: Python
import hmac, hashlib
def verify(raw_body: bytes, signature_header: str, secret: str) -> bool:
expected = hmac.new(secret.encode(), raw_body, hashlib.sha256).hexdigest()
return hmac.compare_digest(expected, signature_header or "")
Request Headers
Every delivery includes at least the following headers:
| Header | Value | Notes |
|---|---|---|
Content-Type |
application/json |
|
Signature |
<hex HMAC-SHA256> |
See Verifying Authenticity. |
User-Agent |
NotificationSystem/0.1 |
|
Authorization |
Bearer <token> |
Optional. Sent only if a bearer token was configured for your subscription. |
Additional custom headers can be configured per subscription on request (for example, an API key your gateway expects). If configured, these are included on every delivery.
Delivery, Retries, and Timeouts
- Method:
POST - Body: JSON (the envelope described in Payload Format)
- Success: Your endpoint must return a
2xxstatus code. Anything else (3xx, 4xx, 5xx), a connection error, or a timeout is treated as a failure. - Timeout: Respond within approximately 2 seconds. Acknowledge with a
2xxand process the event asynchronously on your side. - Retries: Up to 3 attempts total per event, using exponential backoff (roughly 10 seconds before the second attempt and 100 seconds before the third). After the final failed attempt, the event is dropped.
- TLS: Your endpoint must be reachable over HTTPS with a valid certificate.
Idempotency & Ordering
-
Deliver-at-least-once. Because of retries, you may occasionally receive the
same event more than once. Use
ResourceRecordKeytogether withEntityEventTimestampto de-duplicate. - No strict ordering guarantee. Events may arrive out of order, especially across retries. When you fetch the underlying record, prefer its current state and modification timestamp over assuming the event you received is the newest.
Circuit Breaker
To protect both sides during sustained outages, a subscription that accumulates a large number of consecutive final failures will be automatically disabled, and the contact email on the subscription will be notified. Once your endpoint is healthy again, contact us to re-enable the subscription. Missed events during the outage can often be recovered via Catch-up Replay.
Filtering
Each subscription has two independent filters:
-
Resource type — receive only
Property,Member, and/orOfficeevents. An empty filter means all resource types. - MLS — receive events only for specific MLS code(s). An empty filter means all MLSs you are authorized for.
You only receive an event if it matches both filters on your subscription.
Catch-up Replay
If your endpoint was unavailable for a period (maintenance, an outage, or a tripped circuit breaker), historical events can be replayed for a given time window so you can backfill what you missed. Replayed deliveries are identical in shape to live deliveries. Contact api-support@sparkplatform.com with the affected time range to request a replay.
Getting Subscribed
Webhook subscriptions are provisioned by our team. We do not currently offer a self-service registration API. To get started, contact api-support@sparkplatform.com with the following information:
- Destination URL — the HTTPS endpoint that will receive
POSTrequests. - Resource types —
Property,Member,Office, or any combination. - MLS scope — the MLS code(s) you should receive events for.
- Contact email — for delivery-failure and circuit-breaker notifications.
- Auth needs (optional) — a bearer token and/or any custom headers your endpoint requires.
Once provisioned, you will receive the signing secret used to verify the
Signature header. Keep this secret confidential; treat it like a password.
Quick Reference
POST <your-endpoint>
Content-Type: application/json
Signature: <hex hmac-sha256 of raw body, keyed with your secret>
User-Agent: NotificationSystem/0.1
Authorization: Bearer <token> # only if configured
{
"@reso.context": "urn:reso:metadata:2.0:resource:entityevent",
"value": [
{
"ResourceName": "Property",
"ResourceRecordKey": "20260514111441584701000000",
"EntityEventTimestamp": "2026-06-10T14:32:05+00:00",
"EntityEventSourceSystemInternalID": "123",
"SystemID": "M1000123456",
"ResourceRecordModificationTimestamp": "2026-06-10T14:31:58+00:00"
}
]
}