Notifications
Ring provides real-time notifications to partners through webhook-based callbacks. This enables partners to receive timely updates about device events, changes, and user actions.
Overview
The notification system uses:
- HTTP POST webhooks to partner endpoints
- HMAC-SHA256 signature in the
X-Signatureheader for message authenticity - JSON:API structure for consistent payload format
- Event-driven architecture for real-time updates
- Account ID in
metafor user association
Webhook Endpoint Requirements
Before receiving webhooks, your endpoint must meet the following requirements:
| Requirement | Detail |
|---|---|
| Protocol | HTTPS only (TLS 1.2 or higher required) |
| Accessibility | Endpoint must be publicly accessible from Ring's servers |
| Registration | Webhook URL is configured during partner onboarding |
| Response time | Must respond within 5 seconds or Ring treats the delivery as failed |
| Content-Type | Ring sends application/json; your endpoint must accept this content type |
| Method | Ring sends POST requests exclusively |
Webhook Authentication & Verification
HMAC-SHA256 Signature Verification
All webhook requests from Ring include an HMAC-SHA256 signature in the X-Signature header. Partners must verify this signature using the HMAC signing key issued with their partner credentials before processing any webhook payload:
POST https://api.partner.example.com/webhooks/ring
X-Signature: sha256=<hmac_signature>
Content-Type: application/json
Signature Verification
import hmac
import hashlib
def verify_webhook_signature(signing_key, raw_body, received_signature):
"""Verify the HMAC-SHA256 signature of a Ring webhook."""
expected = hmac.new(
signing_key.encode(),
raw_body,
hashlib.sha256
).hexdigest()
received = received_signature.removeprefix("sha256=")
# Constant-time comparison to prevent timing attacks
return hmac.compare_digest(expected, received)
.hexdigest()), while Nonces use URL-safe Base64 without padding. Do not mix encodings.Webhook v1.1 Payload Structure
All Ring webhooks follow the v1.1 payload format with account_id in the meta section:
{
"meta": {
"version": "1.1",
"time": "2026-02-13T13:39:57.200155525Z",
"request_id": "2ad45ade-1818-4154-813b-afdd8bcd8085",
"account_id": "ava1.ring.account.XXXYYY"
},
"data": {
"id": "<device_id>_<event_type>_<timestamp>",
"type": "<event_type>",
"attributes": {
"source": "<device_id>",
"source_type": "devices",
"timestamp": 1770989995231
},
"relationships": {
"devices": {
"links": {
"self": "/v1/devices/<device_id>"
}
}
}
}
}
Meta Fields
- version: Always
1.1for the current webhook specification - time: ISO 8601 timestamp when Ring sent the webhook
- request_id: Unique identifier for this webhook request (use for idempotency)
- account_id: The Account ID of the Ring user associated with this event
Event Types
Ring supports the following webhook event types:
| Event | Type Value | Description | Details |
|---|---|---|---|
| Motion Detected | motion_detected |
Motion detected by a device camera (includes subType such as human) |
Motion Detection |
| Button Press | button_press |
Ring doorbell button pressed | Button Press |
| Device Added | device_added |
A device became accessible to the partner | Device Addition |
| Device Removed | device_removed |
A device is no longer accessible | Device Removal |
| Device Online | device_online |
A device came online | Device Online |
| Device Offline | device_offline |
A device went offline | Device Offline |
Webhook Response Handling
Your endpoint must respond with an appropriate HTTP status code:
| Status Code | Ring Behavior | Details |
|---|---|---|
| 200-299 | Delivery successful | Ring marks the event as delivered. No retry. |
| 400-499 | Permanent failure — no retry | Indicates a client-side issue. Ring will not retry these. |
| 500-599 | Temporary failure — retry with backoff | Ring will retry delivery using exponential backoff. |
| Timeout | Treated as 5xx — retry with backoff | If your endpoint does not respond within 5 seconds, Ring treats it as a server error. |
Retry Logic
Ring implements automatic retry logic for failed webhook deliveries (5xx responses and timeouts):
Retry Schedule
| Attempt | Delay After Previous Attempt |
|---|---|
| 1 (initial) | Immediate |
| 2 | 1 second |
| 3 | 5 seconds |
| 4 | 30 seconds |
| 5 | 2 minutes |
| 6 | 10 minutes |
| 7 | 1 hour |
- Maximum retry attempts: 6 retries (7 total attempts including the initial delivery)
- Dead letter queue: After all retries are exhausted, the event is moved to a dead letter queue. Events are retained for 72 hours. Partners can request a replay of dead-lettered events by contacting their Ring integration representative.
meta.request_id field to deduplicate events.Implementation Best Practices
Webhook Handler Example
from flask import Flask, request, jsonify
import hmac
import hashlib
app = Flask(__name__)
SIGNING_KEY = "your_hmac_signing_key"
@app.route('/webhook/ring', methods=['POST'])
def handle_ring_webhook():
# Verify HMAC signature
signature = request.headers.get('X-Signature', '')
if not verify_webhook_signature(SIGNING_KEY, request.data, signature):
return jsonify({'error': 'Invalid signature'}), 401
# Parse payload
try:
payload = request.get_json()
event_type = payload['data']['type']
account_id = payload['meta']['account_id']
# Route to appropriate handler
if event_type == 'motion_detected':
handle_motion_detection(payload)
elif event_type == 'button_press':
handle_button_press(payload)
elif event_type == 'device_added':
handle_device_addition(payload)
elif event_type == 'device_removed':
handle_device_removal(payload)
elif event_type == 'device_online':
handle_device_online(payload)
elif event_type == 'device_offline':
handle_device_offline(payload)
else:
return jsonify({'error': 'Unknown event type'}), 400
return jsonify({'status': 'processed'}), 200
except Exception as e:
log_error(f"Webhook processing failed: {e}")
return jsonify({'error': 'Processing failed'}), 500
Idempotency Handling
def handle_webhook_with_idempotency(payload):
request_id = payload['meta']['request_id']
if is_already_processed(request_id):
return {'status': 'already_processed'}
result = process_webhook(payload)
mark_as_processed(request_id)
return result
Common Mistakes
| Mistake | Consequence | Fix |
|---|---|---|
| Not verifying HMAC signatures | Accepts spoofed webhooks | Always call verify_webhook_signature() before processing |
| Doing heavy processing in handler | Exceeds 5-second timeout, causes duplicates | Return 200 immediately, process asynchronously |
| Not handling duplicate events | Business logic executes multiple times | Use meta.request_id as idempotency key |
| Returning 4xx for transient errors | Ring will not retry | Return 500 for recoverable errors |
| Ignoring dead letter queue | Missed events during outages | Monitor and request replays within 72-hour window |
Security Considerations
- Verify HMAC signature: Always validate the
X-Signatureheader - Use account_id: Associate events with the correct Ring user via
meta.account_id - Validate payload: Check request structure and required fields
- Idempotency: Handle duplicate deliveries gracefully using
request_id - Rate limiting: Implement appropriate rate limiting on webhook endpoints
- Respond quickly: Return HTTP 200 within 5 seconds
Next: Users API →

