GV

GeezerVison

The World Wide Web, restored to 800 pixels of glory.

Home|About|Gaming cabinet|Archives|How it works|Starter sites

On this site

  • Developer Documentation
  • Ring Developer Experience

This view

  • Original page
  • New address

GeezerVison is showing developer.amazon.com (truncated) — Ring Partner API Documentation | Ring Developer Experience Documentation

Amazon Developer
Alexa
Amazon Appstore
Ring
AWS
Documentation

Console

as

Settings
Sign out
Notifications
Alexa
Amazon Appstore
Ring
AWS
Documentation
Support
Contact Us
My Cases

Console

as

Ring Docs
Ring
Ring Developer Documentation

Collapse All | Expand All


  • Release Notes
  • App Store Process
    • Getting Started
    • Configure
    • Develop
    • Certify
    • Publish
    • Reports
    • Ring MCP
  • UX Design Guide
  • Computer Vision Guidelines
  • Marketing Guidelines
  • Ring API
    • Overview
    • Key Features
    • API Standards
    • Getting Started Steps
    • Quick Links
    • API Reference
    • API Reference Details
      • Base URL
      • Authentication (Bearer token)
      • Rate Limiting
      • Content Types
      • Required HTTP Headers
      • Authentication Endpoints
      • Endpoint Reference
      • Webhook Events
      • Error Responses
      • Testing Your Integration
      • SDKs and Libraries
    • Application Deployment
    • Deployment Steps
      • Prerequisites and Setup
      • App Deployment Journey
      • Service Architecture Overview
      • Endpoint Configuration
      • Token Exchange Flow
      • Account Linking
      • App Integration UX Flow
      • Accessing Devices
      • Webhook Event Delivery
      • Environment Configuration
    • Authentication
    • Account Linking Methods
      • One-Way (Ring-Driven)
      • Partner-Initiated OAuth 2.0
    • Refresh Tokens
    • Access Tokens
    • Users API
    • App Integration
    • Device Discovery
    • Device Details
      • Capabilities
      • Status
      • Locations
      • Configurations
      • Multi-Camera Devices
    • Notifications
      • Notifications
      • Overview
      • Webhook Endpoint Requirements
      • Webhook Authentication & Verification
      • Webhook v1.1 Payload Structure
      • Event Types
      • Webhook Response Handling
      • Retry Logic
      • Common Mistakes
      • Motion Detection
      • Button Press
      • Device Addition
      • Device Removal
      • Device Online
      • Device Offline
      • App Integration Added
      • App Integration Removed
      • Subscription Activated
      • Subscription Deactivated
    • Sensors
      • Sensors
      • Contact Sensors
      • Flood/Freeze Sensor
      • Indoor Temperature/Humidity Sensor
      • Indoor Air Quality Monitor
    • Chimes
      • Chimes
      • Requesting Chime Access
      • Chime Device Discovery
      • Chime Capabilities
      • Chime Configurations
      • Chime Status
      • Playing Audio on a Chime
      • Chime Events
    • Event History
      • Event History
    • Live Video, Media Clips and Image Snapshots
      • Live Video
      • WHEP Live Video Streaming
      • RTSP Live Video Streaming
      • Media Clips
      • Image Snapshots
    • Subscriptions Query API
    • Ring API Use Cases
    • Error Handling
    • Related topics
  • Customer Lifecycle Analytics
  • Developer FAQ
  • Legal Content
    • Ring Appstore Schedule
    • Program Requirements
    • Content Policy
  • Ring App Challenge Onboarding Kit

Ring Partner API Documentation

Overview

Ring provides device capabilities to partners while ensuring a high bar for security, transparency, and control for users. This API enables partners to:

  • Authenticate users through OAuth 2.0 with one-way account linking
  • Discover and access Ring devices with proper permissions
  • Receive real-time notifications for device events
  • Stream live video content using WebRTC/WHEP protocol
  • Download historical video clips in MP4 format
  • Address individual cameras on multi-camera devices such as Ring Elite

Key Features

🔐 Secure Authentication

OAuth 2.0 with account linking ensures user consent and secure token management

📱 Device Discovery

Comprehensive device discovery and configuration through JSON:API specification

🔔 Real-time Notifications

Webhook-based event system for motion detection and device lifecycle events

📹 Video Streaming

WebRTC-based live video with WHEP protocol for low-latency streaming

Warning: All requests to the Amazon Vision API (https://api.amazonvision.com) and the Ring OAuth server (https://oauth.ring.com) must be made server-to-server from your Partner App Backend. Browser-initiated requests (e.g., from JavaScript running in the user's browser) will be blocked by CORS policies on Ring's endpoints.

API Standards

The Ring Partner API follows these standards:

  • JSON:API Specification for most endpoints
  • OAuth 2.0 for authentication
  • WebRTC/WHEP for live video streaming
  • Standard HTTP for media downloads

Getting Started Steps

  1. Review the Architecture - Understand the end-to-end deployment flow before building.

  2. Authentication Setup - Implement OAuth 2.0 token exchange and one-way account linking with HMAC nonce verification.

  3. Retrieve User Profile - Call the Users API to get the Ring Account ID for nonce matching and event correlation.

  4. Confirm Account Link - Complete the two-step POST + PATCH flow to finalize the integration.

  5. Device Discovery - Discover Ring devices, capabilities, and status through JSON:API.

  6. Webhook Notifications - Set up webhook endpoints to receive real-time device events with HMAC signature verification.

  7. Live Video Streaming - Stream live video from Ring devices using WebRTC/WHEP.

  8. Media Clips - Download historical video clips in MP4 format.

  9. Image Snapshots - Download image snapshots in JPEG or PNG format.

Quick Links

  • 🏗️ High Level Architecture
  • 🔐 Authentication Guide
  • 📱 Device Discovery
  • 🎥 Multi-Camera Devices
  • 👤 Users API
  • 🔗 App Integrations
  • 🔔 Webhook Notifications
  • 📹 Live Video Streaming
  • 🎬 Media Clips
  • 📋 Error Handling

API Reference

Base URL

All API requests should be made to:

https://api.amazonvision.com

Authentication

All API requests require authentication using OAuth 2.0 Bearer tokens:

Authorization: Bearer <access_token>

Rate Limiting

  • Default rate: 100 requests per second (TPS) per partner
  • Scope: Rate limits apply per partner client_id across all endpoints

Rate Limit Headers

Header Description
X-RateLimit-Limit Maximum requests allowed per time window
X-RateLimit-Remaining Remaining requests in the current window
Retry-After Seconds to wait before retrying (included with 429 responses)

When you exceed the rate limit, the API returns HTTP 429 Too Many Requests. Use exponential backoff starting at 1 second and respect the Retry-After header.

Content Types

  • JSON APIs: application/json
  • Media streaming: application/sdp
  • Media downloads: Binary content with appropriate MIME types

Required HTTP Headers

All requests to Ring API endpoints (api.amazonvision.com and oauth.ring.com) must include the following headers in addition to the standard Authorization and Content-Type headers:

Header Required Description
User-Agent Yes A string identifying your application. Use the format YourAppName/version (e.g., HomeGuardian/1.0).
Hardware_Id Yes A UUID that uniquely identifies each user and device combination using your system.

Requests that are missing required headers may receive a 406 Not Acceptable response. This status code indicates that Ring's web application firewall has blocked the request.

Example request with required headers:

curl -X GET 'https://api.amazonvision.com/v1/devices' \
  -H 'Authorization: Bearer <access_token>' \
  -H 'Content-Type: application/json' \
  -H 'User-Agent: HomeGuardian/1.0' \
  -H 'Hardware_Id: 550e8400-e29b-41d4-a716-446655440000'
Important: If your backend server makes requests from a datacenter or cloud-hosting IP address, you may also receive 406 responses due to network-level security rules, even with the correct headers. Contact Ring developer support with your static egress IP address to resolve this.

Authentication Endpoints

Exchange Authorization Code

Request:

POST https://oauth.ring.com/oauth/token
Content-Type: application/x-www-form-urlencoded

grant_type=authorization_code
client_id=<partner_client_id>
code=<authorization_code>
client_secret=<partner_client_secret>

Response:

{
  "access_token": "xxxxx",
  "refresh_token": "yyyyy",
  "scope": "<scope>",
  "expires_in": 14400,
  "token_type": "Bearer"
}

Refresh Access Token

Request

POST https://oauth.ring.com/oauth/token
Content-Type: application/x-www-form-urlencoded

grant_type=refresh_token
refresh_token=<refresh_token>
client_id=<partner_client_id>
client_secret=<partner_client_secret>

Response:

{
  "access_token": "xxxxx",
  "refresh_token": "yyyyy",
  "scope": "<scope>",
  "expires_in": 14400,
  "token_type": "Bearer"
}

Endpoint Reference

All device and media endpoints require Authorization: Bearer <access_token>. Responses follow JSON:API format.

On a multi-camera device such as Ring Elite, several endpoints accept an optional component_id query parameter to target one camera module. See Multi-Camera Devices.

Device Endpoints

Method Endpoint Description Full Docs
GET /v1/devices List all accessible devices. Use ?include=status,capabilities,location,configurations for related data. Device Discovery
GET /v1/devices/{device_id} Get a specific device Device Discovery
GET /v1/devices/{device_id}/status Device online/offline status. On a chime, also reports snooze state — see Chime Status. Status
GET /v1/devices/{device_id}/capabilities Doorbell press support, video codecs, motion detection, image enhancements, camera modules. Add ?component_id=N to scope to one camera module. On a chime, returns the audio slot count — see Chime Capabilities. Capabilities
GET /v1/devices/{device_id}/location Coarse location (country, state) for compliance Locations
GET /v1/devices/{device_id}/configurations Motion zones, privacy zones, image settings. Add ?component_id=N to scope to one camera module. On a chime, returns audio slots and volume, read-only — see Chime Configurations. Configurations
GET /v1/history/devices/{device_id}/events Get event history for a device Event History

Media Endpoints

Method Endpoint Description Full Docs
POST /v1/devices/{device_id}/media/streaming/whep/sessions Start WebRTC WHEP live video session (Content-Type: application/sdp). Add ?component_id=N to select a camera module. Live Video
DELETE /v1/devices/{device_id}/media/streaming/whep/sessions/{session_id} Close live video session Live Video
— rtsps://video.rtsp.amazonvision.com:322/v1/devices/{device_id}/stream RTSPS live video stream. Add ?component_id=N to select a camera module. RTSP Live Video Streaming
POST /v1/devices/{device_id}/media/video/download Download historical video as MP4. Pass a components array in the request body to select a camera module. Media Clips
POST /v1/devices/{device_id}/media/image/download Download image snapshot as JPEG or PNG. Pass a components array in the request body to select a camera module. Image Snapshot
POST /v1/devices/{device_id}/media/audio/playback Play audio on a chime, named by audio_ref in the request body. Requires the Chime Controls capability. Chimes

Account & Integration Endpoints

Method Endpoint Description Full Docs
GET /v1/users/me Get authenticated user's Account ID Users API
POST /v1/accounts/me/app-integrations Confirm account link App Integrations
PATCH /v1/accounts/me/app-integrations Update integration status App Integrations
GET /v1/accounts/me/subscriptions List subscriptions and trials for the authenticated user's app Subscriptions Query API

Webhook Events

Partners receive webhook notifications at their configured endpoint. All webhooks include an HMAC-SHA256 signature in the X-Signature header. See Notifications for full details.

Event Types

Event Type Value Description Full Docs
Motion Detected motion_detected Motion detected by a device camera. Includes attributes.sub_type (such as human), plus attributes.component_ids on multi-camera devices 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
App Integration Added app_integration_added A Ring user linked your partner integration App Integration Added
App Integration Removed app_integration_removed A Ring user unlinked your partner integration App Integration Removed
Subscription Activated subscription_activated A paid subscription or trial was activated Subscription Activated
Subscription Deactivated subscription_deactivated A paid subscription or trial was deactivated Subscription Deactivated

Error Responses

All errors follow JSON:API format. See Error Handling for comprehensive details.

Common Error Codes

  • 400: Bad Request — Invalid parameters
  • 401: Unauthorized — Invalid or expired token
  • 403: Forbidden — Insufficient permissions
  • 404: Not Found — Resource not found
  • 406: Not Acceptable — Request blocked by web application firewall
  • 429: Too Many Requests — Rate limit exceeded
  • 500: Internal Server Error
  • 503: Service Unavailable — Device offline

Testing Your Integration

Recommended Tools

There is no official Ring Partner SDK. Use standard HTTP libraries and tools:

Tool Use Case
curl Quick command-line testing
Postman GUI-based API exploration
Bruno Open-source, Git-friendly alternative to Postman
Standard HTTP libraries fetch (JS), requests (Python), axios (JS/TS)

Testing with curl

curl -X GET 'https://api.amazonvision.com/v1/devices?include=status,capabilities' \
  -H 'Authorization: Bearer <access_token>' \
  -H 'Content-Type: application/json' \
  -v

SDKs and Libraries

JavaScript/TypeScript

const response = await fetch('https://api.amazonvision.com/v1/devices', {
  headers: {
    'Authorization': `Bearer ${accessToken}`,
    'Content-Type': 'application/json'
  }
});
const devices = await response.json();

Python

import requests

headers = {
    'Authorization': f'Bearer {access_token}',
    'Content-Type': 'application/json'
}
response = requests.get('https://api.amazonvision.com/v1/devices', headers=headers)
devices = response.json()

Application Deployment

This document defines the end-to-end deployment architecture for a partner application integrating with the Ring AppStore. It covers the complete journey from initial app creation and credential setup through endpoint configuration, authentication orchestration, device access, and webhook event delivery.


1. Prerequisites and Setup

Before deploying your application, you must create your app in the Ring Developer Portal, obtain credentials, configure your endpoints, and prepare your backend services.

1.1 Create Your Application

Register your application in the Ring Developer Portal. See Configure Your Ring Application for the complete step-by-step registration process.

  1. Click Create new app and provide your app name
  2. Accept Ring's security and privacy standards
  3. Complete the app information form (description, category, website, support email, privacy policy, terms of service, app icon)

1.2 Obtain Credentials

When you create your application, Ring issues three credentials. These are shown only once — store them securely before continuing.

Credential Purpose
Client ID Identifies your app in OAuth token requests
Client Secret Authenticates your app during token exchange and refresh
HMAC Signing Key Verifies webhook signatures and generates nonces during account linking

These credentials are shared across staging and production environments. Never commit them to version control — use environment variables or a secrets manager.

There's no way to regenerate credentials on an existing app. If you lose them, or you need to replace them after an exposure, delete the app and create a new one, which issues a fresh set of all three. Use this approach only before you submit the app for certification; after submission, contact Ring support. See Replacing Your App Credentials.

1.3 Configure Endpoints

Register the following HTTPS endpoints in the Ring Developer Portal:

Endpoint Purpose Required
Token Exchange URL Receives Ring OAuth authorization codes for token exchange Yes
Account Link URL User login page for nonce-based account association Yes
Webhook URL Receives real-time event notifications from Ring devices Yes
App Homepage URL Custom app configuration page for post-linking setup Yes

Configure staging endpoints first, validate your integration, then configure production endpoints. See Configure Your Ring Application for detailed requirements.

1.4 Set Up Your Backend

Before a Ring user installs your app, your backend must be ready to handle:

  • Token exchange — Receive authorization codes at your Token Exchange URL, exchange them within 60 seconds, then return HTTP 200 (Section 5.3)
  • Token storage — Securely store access tokens (~4 hour lifetime) and refresh tokens (~30 day lifetime) with associated Account IDs
  • Nonce matching — Implement HMAC-SHA256 nonce verification for account linking (Nonce Validation and Account Linking)
  • Webhook handler — Accept HTTPS POST requests, verify HMAC signatures, and return HTTP 200 within 5 seconds (Section 9)
  • API client — Call Amazon Vision APIs with Bearer token authentication for device discovery, live video, and media downloads (Section 8)

2. App Deployment Journey

2.1 Lifecycle Overview

App Lifecycle Overview

2.2 Phase Summary

Phase Description Reference
Create App Register in Ring Developer Portal, provide app information Configure Your Ring Application
Obtain Credentials Receive Client ID, Client Secret, HMAC Signing Key Section 1.2
Configure Endpoints Register Token Exchange, Account Link, Webhook, and App Homepage URLs Section 4
Build Integration Implement token exchange, nonce matching, device access, webhook handling Section 5 through Section 9
Test & Certify Validate in staging, then submit for Ring review Certify Your Ring Application
Publish Deploy to Ring AppStore Publish Your Ring Application
User Installs App Ring user discovers app, selects devices, confirms scopes Section 7
Token Exchange Ring sends auth code, partner exchanges for OAuth tokens Section 5
Account Linking Nonce-based verification binds Ring user to partner account Section 6
Device Access Discover devices, stream live video, download media clips Section 8
Webhook Events Receive real-time motion, doorbell, device status notifications Section 9

3. Service Architecture Overview

The Ring AppStore follows a one-way account linking model where Ring manages the OAuth credential lifecycle and partners verify messages through HMAC digital signatures. Partners do not need to operate as OAuth servers.

3.1 Security

Ring takes security seriously. Our account linking mechanism ensures security through cryptographic verification, time-bound validation, and mandatory user authentication. All communications use HTTPS, and access tokens are scoped to specific devices and operations authorized by the user.

3.2 High-Level Service Architecture

High-Level Service Architecture

3.3 Component Responsibilities

Component Role
Ring AppStore User-facing interface — AppStore browsing, app installation, device selection, scope consent, integration management
Ring Backend Orchestrates all server-side logic — OAuth credential lifecycle, app state management, nonce generation, device authorization, notification delivery, HMAC key management
Amazon Vision API External API layer for partners — device discovery, device status/capabilities, live video streaming, media downloads, account integration verification
Partner App Backend Partner server — receives OAuth tokens, exchanges auth codes, stores tokens, handles webhook events, calls Amazon Vision APIs, verifies nonce during account linking
Partner App UI Partner web interface — user login portal for account linking, app configuration pages

4. Endpoint Configuration

Partners must configure endpoint categories to complete an integration. These are registered via the Ring Developer Portal during app creation.

4.1 Partner-Hosted Endpoints

Endpoint Purpose Required Example
Token Exchange URL Receives Ring OAuth authorization codes for token exchange Yes https://api.partner.example.com/oauth/token
Webhook URL Receives real-time event notifications from Ring Yes https://api.partner.example.com/webhooks/ring
Account Link URL Partner sign-in page for nonce-based token claiming (sign-in is mandatory) Yes https://partner.example.com/ring/link
App Homepage URL Custom app configuration page Yes https://partner.example.com/ring/config

4.2 Ring-Hosted Endpoints — Amazon Vision API

Endpoint Method Purpose
https://oauth.ring.com/oauth/token POST Token exchange — authorization code to access/refresh tokens
https://api.amazonvision.com/v1/devices GET Device discovery
https://api.amazonvision.com/v1/devices/{id}/status GET Device online/offline status
https://api.amazonvision.com/v1/devices/{id}/capabilities GET Device capabilities and codecs
https://api.amazonvision.com/v1/devices/{id}/media/streaming/whep/sessions POST Start live video stream via WebRTC WHEP
https://api.amazonvision.com/v1/devices/{id}/media/video/download POST Download recorded video clips
https://api.amazonvision.com/v1/users/me GET Get user profile — returns Account ID
https://api.amazonvision.com/v1/accounts/me/app-integrations POST Partner confirms account link
https://api.amazonvision.com/v1/accounts/me/app-integrations PATCH Partner updates integration status

See API Reference for full details.


5. Token Exchange Flow

After a Ring user installs a partner app, Ring Backend generates an OAuth authorization code and sends it to the Partner App Backend.

5.1 What Triggers This Flow

  1. The user discovers your app in the Ring AppStore
  2. The user reviews the data access scopes your app requests
  3. The user selects which Ring devices to share with your app

When the user completes device selection, Ring Backend generates an OAuth authorization code and sends it directly to your Token Exchange URL (backend-to-backend).

5.2 Token Exchange Sequence

Token Exchange Sequence

5.3 Handling the Authorization Code Request

Ring sends the authorization code to your Token Exchange URL as a form-encoded POST. Parse the body as form data, not JSON: with JSON parsing, code comes back empty and the link fails without an obvious error.

POST https://api.partner.example.com/oauth/token
Content-Type: application/x-www-form-urlencoded

code=<authorization_code>&client_id=<your_client_id>
Field Description
code The authorization code. Single use, and expires 60 seconds after Ring issues it.
client_id Your app's client ID.

Read the fields by name rather than assuming a fixed set.

Unlike webhooks, this request isn't one you can verify by signature. Its security comes from the code itself: it works once, expires in 60 seconds, and is useless without your client secret. The HMAC nonce in the account linking step then binds the tokens to one Ring user.

Handle the request in this order, and return 200 only at the end:

  1. Exchange the code at POST https://oauth.ring.com/oauth/token (see Exchange Authorization Code).
  2. Call GET https://api.amazonvision.com/v1/users/me with the new access token to get the Ring Account ID (see Users API).
  3. Store the tokens and the Account ID as unclaimed.
  4. Return HTTP 200.

If any step fails, return a non-2xx status. Ring treats a non-2xx response as a failed link and doesn't retry the delivery, so build the handler to succeed on the first call, keep it fast, and make it idempotent.

Ring issues a new authorization code every time a user links, including when they link again. You can therefore receive tokens for a Ring Account ID you already hold. Keep the newest pair, and attach it to the partner account that signs in during account linking, which may not be the one linked before.

5.4 Token Lifecycle

Token Lifetime Purpose
Authorization Code 60 seconds One-time use; exchanged for tokens
Access Token ~4 hours Bearer token for Amazon Vision API calls
Refresh Token ~30 Days Used to obtain new access tokens

5.5 Token Refresh

POST https://oauth.ring.com/oauth/token
Content-Type: application/x-www-form-urlencoded

grant_type=refresh_token&refresh_token=<refresh_token>&client_id=<client_id>&client_secret=<client_secret>

See Refresh Tokens for implementation guidance.


6. Account Linking

At this point in the deployment journey the Ring user links their Ring account to your partner account. Ring releases OAuth credentials to your backend upfront, redirects the user to your Account Link URL with an HMAC-signed nonce, and your backend matches that nonce to the correct unclaimed token before calling the App-Integrations API to activate the integration.

The full specification for this step — the design rationale, the sequence diagram, the nonce technical reference, and the seven implementation steps — is in One-Way Account Linking, alongside the partner-initiated alternative.


7. App Integration Flow

This section shows the end-to-end app integration experience from two perspectives: a step-by-step walkthrough of the complete account linking flow, and a condensed UX flow diagram that maps the same journey across system components.

7.1 Complete Account Linking Flow

The following flow shows each step a Ring user and your backend go through during account linking, from app discovery to a confirmed integration.

[UI] Step 1
Ring user selects app to enable
User browses available apps and selects the app they want to enable
→
[UI] Step 2
Ring user sees app details
User views the details and information about the selected app
→
[UI] Step 3
Ring user consents to data access
User reviews and provides consent for the app to access their Ring data
→
[UI] Step 4
Ring user selects device to subscribe
User chooses which Ring device(s) they want to connect with the app
→
[S2S] Step 5
Token Exchange Sequence starts
Partner Backend exchanges the code, then returns 200
→
[S2S] Step 6
Redirects to Partner Account Link URL
Backend redirect to partner linking endpoint
→
[UI] Step 7
User clicks Sign in — redirected to app login
User is prompted to sign in and redirected to the partner app's login page
→
[UI] Step 8
User creates account or logs in
User creates a new account or logs into existing account
→
[S2S] Step 9
Partner Backend verifies user & POSTs the nonce
POST app-integrations — Ring verifies the nonce and activates device access
→
[S2S] Step 10
app_integration_added and device_added webhooks sent
One device_added per shared device, within seconds of the POST, while the integration is still awaiting
→
[S2S] Step 11
Partner Backend completes linking
PATCH app-integrations with status completed
→
[UI] Step 12
User sees "Account Linked Successfully"
User receives confirmation that account has been successfully linked
→
[UI] Step 13
User sees Active on Integrations Dashboard
User can view the active integration status in their Ring dashboard
Step 1: Ring user selects app to enable
Step 2: Ring user sees app details
Step 3: Ring user consents to data access
Step 4: Ring user selects device to subscribe
Step 7: User clicks Sign in and is redirected to app login
Step 8: User creates account or logs in
Step 12: User sees Account Linked Successfully

7.2 App Integration UX Flow

The following diagram shows the same integration journey as a sequence across the Ring AppStore, Ring API, Partner App UI, and Partner App Backend.

App Integration UX Flow

8. Accessing Devices

Once the integration reaches awaiting or completed status, the Partner App Backend can access authorized Ring devices through the Ring API.

See: Device Discovery · Live Video · Media Clips . Image Snapshots

8.1 Device Access Sequence

Device Access Sequence

8.2 Authentication Header

All Ring API calls require a valid access token:

Authorization: Bearer <access_token>

If the token has expired, refresh it before making API calls. See Refresh Tokens.


9. Webhook Event Delivery

Ring Backend delivers real-time event notifications to the Partner App Backend via signed webhooks using HMAC-SHA256 signatures.

See: Notifications

9.1 Webhook Delivery Sequence

Webhook Delivery Sequence

9.2 Event Types

Event Type Value Description Details
Motion Detected motion_detected Motion detected (includes attributes.sub_type such as human) Motion Detection
Button Press button_press Doorbell button pressed Button Press
Device Added device_added Device becomes available to partner Device Addition
Device Removed device_removed Partner loses access to a device Device Removal
Device Online device_online Device comes online Device Online
Device Offline device_offline Device goes offline Device Offline

10. Environment Configuration

Component Production
Ring API https://api.amazonvision.com
OAuth Server https://oauth.ring.com
Dev Portal https://developer.amazon.com/ring/console/apps

Authentication

Ring utilizes OAuth 2.0 standard tokens for authentication to ensure secure communication with partner cloud services. The authentication system is based on an "account linking" mechanism that provides user-scoped authentication materials while maintaining customer awareness and control.

Note: Two account linking methods — Ring AppStore supports two account linking methods. One-way account linking (Ring-driven) is the recommended method for Ring-initiated flows: Ring releases OAuth credentials upfront, eliminating the need for partners to operate as OAuth servers, and uses an HMAC-based nonce to cryptographically bind each account linking redirect to a specific Ring user — partners match this nonce to claim the correct token. See One-Way Account Linking. Partner-Initiated OAuth 2.0 lets partners start the linking flow from their own platforms using a standard OAuth 2.0 authorization code flow with PKCE — currently available by invitation only. See Partner-Initiated OAuth 2.0 Account Linking.

Account Linking Methods

The following table shows the key similarities and differences between the two account linking methods.

Aspect One-Way (Ring-Driven) Partner-Initiated OAuth 2.0
Flow initiator Ring AppStore Partner's app or platform
Ring login page shown No (user already signed in) Yes — user authenticates at Ring
Permission agreement Yes Yes (reuses existing UI)
Device selection Yes Yes (reuses existing UI)
PKCE required No Yes
Token delivery Ring POSTs auth code to partner's Token Exchange URL Partner calls Ring after redirect with auth code
HMAC nonce Yes — validates Ring-to-partner redirect Not used — Bearer token is proof of possession
Webhook authentication HMAC-SHA256 signed with shared signing key HMAC-SHA256 signed with shared signing key
Token storage Partner stores Ring tokens Partner stores Ring tokens
Free or paid Both Free only (current phase)
Status Current recommended for Ring-initiated flows New — available by invitation

Overview

The authentication flow involves:

  1. Account Linking: Users authenticate with partner systems
  2. Token Exchange: Authorization codes are exchanged for access/refresh tokens
  3. Token Management: Refresh tokens maintain long-term access

Key Components

  • User-scoped tokens: Each token pair is associated with a specific Ring user
  • Customer confirmation: Users must confirm the partner account before token release
  • Webhook notifications: Tokens enable Ring to deliver signed webhook notifications to partners

Authentication Flow

Ring Application Life Cycle

Token Types

Access Tokens

  • Used for API requests
  • Short-lived (typically 4 hours)
  • Include in Authorization: Bearer <token> header
  • See Access Tokens for details

Refresh Tokens

  • Used to obtain new access tokens
  • Valid for approximately 30 days
  • Must be refreshed proactively before expiry — an expired refresh token requires the user to re-link their account
  • Critical for maintaining continuous access
  • See Refresh Tokens for details
Warning: Common Mistake: Refresh tokens cannot be used as Bearer tokens. If you place a refresh token in the Authorization: Bearer header, API calls will fail with 401 Unauthorized. You must first exchange the refresh token for an access token via POST https://oauth.ring.com/oauth/token. See Refresh Tokens for the exchange flow.

Quick Start: If You Already Have a Refresh Token

If you already have a refresh token (e.g., from a completed account linking flow) and need to start making API calls:

  1. Obtain your client_id and client_secret — These are issued during partner onboarding.
  2. Exchange the refresh token for an access token — Call POST https://oauth.ring.com/oauth/token with grant_type=refresh_token. See Refresh Token Exchange.
  3. Use the access token in API requests — Include in the Authorization: Bearer <access_token> header.
POST https://oauth.ring.com/oauth/token
Content-Type: application/x-www-form-urlencoded

grant_type=refresh_token&refresh_token=<your_refresh_token>&client_id=<your_client_id>&client_secret=<your_client_secret>

The response will include a new access_token (valid ~4 hours) and a new refresh_token. Store both securely.

Security Requirements

  • All authentication materials are user-scoped
  • Partners must use correct tokens for specific user data
  • Token storage and handling must follow security best practices
  • Regular token refresh is required to maintain access

Sub-pages

  • One-Way Account Linking — OAuth account linking flow (one-way recommended)
  • Partner-Initiated OAuth 2.0 — Partner-initiated account linking with PKCE (invitation only)
  • Refresh Tokens — Token refresh and management
  • Access Tokens — Using access tokens in API requests

One-Way Account Linking

The one-way account linking model eliminates the need for partners to act as OAuth servers. Ring releases credentials upfront and redirects the user to the partner for account association.

For the partner-initiated alternative, see Partner-Initiated OAuth 2.0 Account Linking. For a side-by-side comparison of both methods, see Account Linking Methods in the Authentication section.

Note: Reduce drop-off with passwordless account linking — Requiring users to sign in with a password at the account linking step is one of the biggest sources of drop-off. Consider implementing passwordless account linking, which lets users complete the link without a password. See Reducing Account Linking Drop-Off in the UX Design Guide for implementation guidance.

See also: App Integrations API

Why One-Way Account Linking Is Designed the Way It Is

The one-way account linking flow solves a specific problem: how do you securely associate a Ring user's OAuth tokens with the correct partner user, when the tokens arrive on the backend before the partner even knows who the user is?

The Core Challenge

Ring wants to release OAuth credentials upfront — before the user logs into the partner service. This means the partner receives tokens for a Ring user but has no idea which partner user they belong to. The tokens are "unclaimed."

Why Not Just Send the Ring Account ID in the Redirect?

Because the redirect goes through the user's browser. Anything in the URL can be intercepted, modified, or forged. An attacker could swap the Account ID and trick the partner into linking the wrong Ring account to their partner account. There's no way to verify the value is legitimate.

Why the HMAC Nonce?

The nonce is a cryptographic proof that the redirect came from Ring and corresponds to a specific Ring user. It's computed as HMAC-SHA256(K_hmac, "<time>:<account_id>") using a shared secret that only Ring and the partner know: the HMAC Key. An attacker can't forge it without the HMAC key. The partner can verify it by recomputing the HMAC against each unclaimed token's Account ID and prefixing it with the time provided in the redirect — only the correct one will match.

Why Does the Partner Need to Call /v1/users/me Separately?

To get the Account ID through a trusted backend channel (the access token), not from the browser. This keeps the Account ID out of the URL entirely. The partner stores it server-side, and later uses it to verify the nonce — also server-side.

Why Is a Verified Partner Identity Required?

The requirement is not that the customer signs in. It is that the identity you attach to the claimed token comes from a source Ring can trust: the Ring email returned by the Users API for the token that matched the nonce. Never attach an identity supplied by the browser. Anyone who intercepts the nonce redirect can send any email address.

There are two ways to meet this requirement:

  • Passwordless (recommended). Fetch the customer's Ring email server-side with the nonce-matched token and link automatically. No sign-in, no form. The verified identity still comes from the token, so the requirement is met with zero customer input.
  • Partner sign-in. The customer signs in or creates an account, and you derive the identity from that authenticated session. Use this when your service needs its own credentials.

Both are valid. What is mandatory is the identity binding, not the sign-in.

Implementation requirements

Do the linking work on your server. Match the nonce, look up the customer's email with the Users API, and claim the token on your server. The browser should only pass nonce and time along.

Check that the link is fresh everywhere it's used. Any server endpoint that receives nonce and time must reject the request if time is more than 600 seconds old or in the future. This applies to every such endpoint, not only the page Ring first redirects to.

Keep sensitive data out of URLs. URLs are saved in browser history and server logs, and they can leak to other websites. Treat anything in a URL as public.

  • Ring email: Don't add it to a redirect URL. Have your server include it in the page it returns, or have the page request it from your backend.
  • Signing the customer in after linking: Don't put your app's sign-in tokens in the redirect URL. Set a secure cookie instead, or redirect with a one-time code that expires within a minute and that your app exchanges with your backend.
  • Ring access and refresh tokens: Never send them to the browser. Keep them on your server.

Why the Two-Step POST + PATCH?

The POST sends the nonce back to Ring for server-side verification and transitions the integration to awaiting. The PATCH lets the partner signal "I'm fully configured" and moves to completed. This separation allows partners that need additional setup steps after linking to do so before finalizing.

Summary

The design boils down to: Ring releases credentials early (better UX, simpler partner infrastructure), and the HMAC nonce is the mechanism that securely binds those credentials to the right user despite the tokens arriving before the user identifies themselves on the partner side.

Full Account Linking Sequence

Full Account Linking Sequence

Nonce Validation and Account Linking — Step-by-Step

The nonce mechanism prevents replay attacks and cryptographically binds each linking attempt to a specific Ring user.

Prerequisites

  • Partner App Backend possesses an unclaimed OAuth token. After receiving the token, the partner retrieves the user's Account ID by calling GET https://api.amazonvision.com/v1/users/me (see Users API).
  • Both Ring Backend and Partner App Backend share an HMAC signing key (K_hmac) exchanged during partner onboarding.
  • Algorithm: HMAC-SHA256.

Nonce Technical Reference

The nonce mechanism uses HMAC-SHA256 with a time-bound validation window to ensure secure account linking.

Property Value
Algorithm HMAC-SHA256
Validation window 600 seconds (10 minutes)
Output encoding URL-safe Base64, no padding (RFC 4648 §5, without = characters)
Warning: Encoding matters: The nonce uses URL-safe Base64 characters (A-Z, a-z, 0-9, -, _) with no = padding. Standard Base64 (which uses +, /, and = padding) will produce a different string. In Java: Base64.getUrlEncoder().withoutPadding(), in Python: base64.urlsafe_b64encode(...).rstrip(b'=').
Note: Same key, different encoding: The HMAC signing key is also used for webhook signature verification (see Notifications), which uses hex encoding (.hexdigest() + sha256= prefix). Nonces use URL-safe Base64 encoding instead. Do not mix the two.

Step 1: Ring Backend Generates Nonce and Redirects User

When the user finishes approving data access and selecting devices in the Ring AppStore, Ring Backend generates a cryptographically signed nonce and redirects the user to the partner Account Link URL:

https://partner.example.com/ring/link?nonce=yT8jdW_nu2W4gR6FI-l8hkPpt_c9EAf4DJ9CTIcuM7c&time=1771130906289

Step 2: User Arrives at Partner App UI

The user lands on the Partner App UI login page with the nonce parameters in the URL.

Important: This page must present a sign-in or create-account form. The sign-in establishes the partner-side user identity required to claim the unclaimed token. Do not build this as an automatic nonce-verification endpoint — nonce matching must only execute after the user has successfully signed in.

Partner App Backend extracts time and nonce, then performs a freshness check:

VALIDATION_WINDOW_SECONDS = 600  # 10 minutes

current_time_ms = int(time.time() * 1000)
time_param_ms = int(request.args['time'])
time_delta_seconds = (current_time_ms - time_param_ms) / 1000

if time_delta_seconds > VALIDATION_WINDOW_SECONDS:
    raise Exception('Link request expired')
if time_delta_seconds < 0:
    raise Exception('Invalid timestamp: cannot be in the future')

Step 3: User Authenticates with Partner

The user signs in to the Partner App UI. The Partner App Backend authenticates the user and obtains their partner-side account information.

Why sign-in is mandatory:

  1. Identifies the partner user — without sign-in, the partner has no verified way to know which user is performing the linking
  2. Provides the account_identifier — the signed-in user's email (masked) is used when calling the App-Integrations API
  3. Prevents blind token claiming — without explicit authentication, tokens would be claimed without user verification

Step 4: Partner Matches Nonce to Unclaimed Token

import hmac
import hashlib
import base64

def compute_nonce(time_param, account_id, hmac_key):
    """Compute nonce using the same algorithm as Ring Backend."""
    payload = f"{time_param}:{account_id}"
    mac = hmac.new(
        hmac_key.encode('utf-8'),
        payload.encode('utf-8'),
        hashlib.sha256
    ).digest()
    return base64.urlsafe_b64encode(mac).rstrip(b'=').decode('utf-8')

def match_nonce_to_token(received_nonce, time_param, hmac_key):
    unclaimed_tokens = get_unclaimed_tokens()

    for token_record in unclaimed_tokens:
        account_id = token_record.account_id
        computed_nonce = compute_nonce(time_param, account_id, hmac_key)

        # Constant-time comparison to prevent timing attacks
        if hmac.compare_digest(computed_nonce, received_nonce):
            return token_record  # Match found

    return None  # No match

Step 5: Partner Claims Token and Calls App-Integrations API

POST https://api.amazonvision.com/v1/accounts/me/app-integrations
Authorization: Bearer <matched_ava_token>
Content-Type: application/json

{
  "account_identifier": "u***[email protected]",
  "nonce": "yT8jdW_nu2W4gR6FI-l8hkPpt_c9EAf4DJ9CTIcuM7c"
}

See App Integrations API for full endpoint details.

Step 6: Ring Backend Verifies and Activates

Ring Backend verifies the nonce, transitions status to awaiting, activates device-level consents, and sends a confirmation email to the Ring user. Ring also sends an app_integration_added webhook and one device_added webhook for each shared device. These can arrive before your POST call returns.

Step 7: Partner Calls PATCH to Complete Integration

PATCH https://api.amazonvision.com/v1/accounts/me/app-integrations
Authorization: Bearer <matched_ava_token>
Content-Type: application/json

{
  "status": "completed"
}
Important: This step is mandatory. Without calling PATCH with status: completed, the integration remains in awaiting state and is not fully operational. Required flow: POST (nonce verification → awaiting) → PATCH (status update → completed).

Security Considerations

The nonce-based account linking mechanism provides protection against common attack vectors including replay attacks, token interception, and unauthorized token claiming. The time-bound validation window and cryptographic verification ensure that only legitimate linking attempts succeed.

Partner-Initiated OAuth 2.0 Account Linking

This section describes Ring AppStore's Partner-Initiated OAuth 2.0 Account Linking — an account linking method that enables partners to start the linking flow from their own platforms. With this method, your app presents a "Connect Ring Account" button; the user is redirected to Ring for login, consent, and device selection; and Ring redirects the user back to your app with a standard OAuth 2.0 authorization code.

This is a standard OAuth 2.0 authorization code flow with PKCE (S256). If you have implemented OAuth 2.0 before, the mechanics are familiar.

For the Ring-driven alternative, see One-Way Account Linking. For a side-by-side comparison of both methods, see Account Linking Methods in the Authentication section.

Availability: Partner-Initiated OAuth 2.0 is currently available by invitation only. Contact your Ring AppStore partner representative to request access.

Terminology

This section uses the following terms consistently:

Term Meaning
Ring base plan A Ring subscription that activates cloud features (video history, snapshots) on Ring devices. Users must have an active Ring base plan on a device before sharing it with a partner app. This is a prerequisite for device eligibility — it is not related to whether the partner app charges users.
Free app An app listed on Ring AppStore that is free for users — no payment required to use it. All Partner-Initiated OAuth 2.0 apps are free apps in the current phase.
Paid app An app listed on Ring AppStore that charges users a fee to use it. Paid app support for Partner-Initiated OAuth 2.0 is planned for a future phase.
Subscription activation The step in the account linking flow where a user subscribes to a paid app. This step is skipped for free apps. Not applicable to Partner-Initiated OAuth 2.0 in the current phase.
Billing setup The process where the developer sets the app price working with a Ring onbarding manager.
Account linking The process of connecting a Ring user's account with a partner service so the partner can access authorized Ring devices via API.
PKCE (S256) Proof Key for Code Exchange — a security extension to OAuth 2.0 that prevents authorization code interception. Mandatory for Partner-Initiated OAuth 2.0.

Terminology note: "Ring base plan" is unrelated to whether an app is free or paid. A Ring base plan is what users pay Ring for cloud features on their devices — it determines which devices are eligible for sharing. Whether users pay the partner to use the app is a separate concept (free app vs. paid app). A user needs a Ring base plan on their devices regardless of whether the partner app is free or paid.

Flow Overview

The following diagram illustrates the Partner-Initiated OAuth 2.0 flow. The initiating participant is on the left, so reading left to right follows the flow.

Key characteristics:

  • Partner initiates the flow — user never needs to visit Ring AppStore first
  • Standard OAuth 2.0 authorization code + PKCE
  • No nonce, no unclaimed token pool — the Bearer token proves possession
  • Partner confirms the link via PATCH /v1/accounts/me/app-integrations

For the one-way (Ring-driven) flow diagram, see One-Way Account Linking.

Prerequisites

Before implementing Partner-Initiated OAuth 2.0 account linking, confirm that:

  1. Your app is created in the Ring Developer Portal. App creation, certification, and publishing follow the same process as any other Ring AppStore app — see Getting Started.
  2. Your app is allowlisted for Partner-Initiated OAuth 2.0 (see Requesting access below).
  3. You have your partner credentials: client_id, client_secret, and hmac_signing_key — issued during onboarding via the Ring Developer Portal.

Requesting access

Partner-Initiated OAuth 2.0 is gated behind an internal allowlist. To request access and have your app shown as Free, open a support case, select Type of Inquiry: Ring Appstore, and include the following information:

Information needed How to obtain it
Vendor ID Sign in at https://developer.amazon.com/settings/console/mycid
App ID Sign in at https://developer.amazon.com/ring/console/apps then navigate to your app and provide the key in the URL on screen, which is between apps and overview

After allowlisting is complete, the Partner-Initiated OAuth 2.0 configuration fields become available on the Account Linking page of your app in the Developer Portal.

Developer Portal Configuration

After your app is allowlisted, configure the account linking settings in the Ring Developer Portal.

Account linking fields

Navigate to your app → Account linking. The page has separate Staging settings and Production settings tabs — configure both. Under Authorization, provide the following:

Field Description Required
OAuth Redirect URI Pre-registered HTTPS URIs that Ring redirects the user to after authorization, with the authorization code. Must exactly match the redirect_uri parameter in the authorization request. Yes
Account Link URL Where account linking starts. This is the page on your platform that begins the flow by redirecting the user to Ring's authorization endpoint. If a user starts from your Ring AppStore listing instead, Ring forwards them here. Yes
App Homepage URL A single link that handles routing for all users — for example, your web app or a smart routing link. Use this when you need one entry point that routes users to the right destination across platforms. Yes
Webhook URL Your endpoint for receiving Ring event notifications. Ring sends POST requests for events like motion detection, device additions, and device removals. Yes

All four values must use HTTPS.

Webhook URL is required even if you plan to poll. You must register an endpoint that returns 200. If you aren't ready to process webhooks at launch, acknowledge deliveries with 200 and use the Event History API instead — see API polling alternative.

Staging and production settings

Configure each environment on its own tab:

  • Staging settings — used while you develop and test your integration
  • Production settings — used by live Ring users after your app is certified and published

Save each tab separately. Values do not carry over between environments, so register the redirect URIs for each environment where your app runs.

Redirect URI rules

  • All redirect URIs must use HTTPS
  • Ring validates using exact match — scheme, domain, path, and port must match precisely
  • No wildcard or prefix matching

What stays the same

Everything outside of account linking configuration is unchanged:

  • App creation and metadata (name, description, icon, supported devices)
  • Certification and review process
  • Publishing to Ring AppStore
  • Scopes — declared and approved during app submission, not configured separately

Implementation Guide

This section walks through the complete implementation of Partner-Initiated OAuth 2.0 account linking.

Step 1: Generate PKCE parameters

Before redirecting the user, generate a PKCE code verifier and code challenge.

import secrets
import hashlib
import base64

def generate_pkce():
    """Generate PKCE code_verifier and code_challenge (S256)."""
    # Generate a random code verifier (43-128 characters)
    code_verifier = secrets.token_urlsafe(32)

    # Compute SHA-256 hash and Base64URL-encode it (no padding)
    digest = hashlib.sha256(code_verifier.encode("ascii")).digest()
    code_challenge = base64.urlsafe_b64encode(digest).rstrip(b"=").decode("ascii")

    return code_verifier, code_challenge

Store code_verifier securely in your session — you need it for the token exchange in Step 4.

Step 2: Redirect user to Ring

This is the work your Account Link URL performs. Users reach that page either by choosing "Connect Ring Account" in your app or by being forwarded from your Ring AppStore listing. Either way, the page redirects the user's browser to Ring's authorization endpoint with the following parameters:

https://account.ring.com/account/integrations/partner-link/authorize
  ?client_id=<your_client_id>
  &redirect_uri=<your_registered_redirect_uri>
  &response_type=code
  &scope=<approved_scope>
  &state=<random_csrf_token>
  &code_challenge=<code_challenge>
  &code_challenge_method=S256
Authorization request parameters
Parameter Required Description
client_id Yes Your Ring OAuth client ID (issued during onboarding)
redirect_uri Yes One of your pre-registered redirect URIs — must exactly match
response_type Yes Always code
scope Yes Only the scope ava.v1:read is supported currently
state Yes An opaque, random string for CSRF protection. Store it in your session and validate it on the callback.
code_challenge Yes Base64URL-encoded SHA-256 hash of your code_verifier
code_challenge_method Yes Always S256
Example redirect
from flask import redirect, session
import secrets

@app.route("/connect-ring")
def connect_ring():
    # Generate PKCE pair
    code_verifier, code_challenge = generate_pkce()

    # Generate CSRF state token
    state = secrets.token_urlsafe(16)

    # Store both in session for later validation
    session["pkce_code_verifier"] = code_verifier
    session["oauth_state"] = state

    # Build the authorization URL
    auth_url = (
        "https://account.ring.com/account/integrations/partner-link/authorize"
        f"?client_id={CLIENT_ID}"
        f"&redirect_uri={REDIRECT_URI}"
        f"&response_type=code"
        f"&scope=ava.v1:read"
        f"&state={state}"
        f"&code_challenge={code_challenge}"
        f"&code_challenge_method=S256"
    )

    return redirect(auth_url)
What happens at Ring

After you redirect the user:

  1. Login — If the user is not signed in to Ring, they see the Ring login page (with 2FA if enabled on their account)
  2. Permission agreement — The user reviews and approves the data access scopes your app requests
  3. Device selection — The user selects which Ring devices to share with your app. Only devices covered by an active Ring base plan are selectable.
  4. Redirect back — Ring redirects the user to your redirect_uri with ?code=<authorization_code>&state=<your_state>

Ring base plan requirement: Users can only share devices that have an active Ring base plan. If no devices are covered, the user is informed they need a Ring base plan and the flow cannot proceed. The user must return to your app and re-initiate after purchasing a plan.

Step 3: Handle the callback

When Ring redirects the user back to your redirect_uri, validate the response and extract the authorization code.

Successful callback
https://your-app.example.com/callback?code=<authorization_code>&state=<your_state>
Error callback

If the user cancels or an error occurs, Ring redirects with an OAuth error:

https://your-app.example.com/callback?error=<error_code>&error_description=<description>&state=<your_state>
Error code Description
access_denied User cancelled the flow, denied consent, or has no devices with an active Ring base plan. Check error_description to tell these apart.
server_error An unexpected error occurred on Ring's side
invalid_scope Requested scope is not approved for your app

For the full list of error_description values and how to handle each one, see Error Handling Reference.

Callback handler example
@app.route("/callback")
def oauth_callback():
    # Validate state to prevent CSRF
    received_state = request.args.get("state")
    if received_state != session.get("oauth_state"):
        return "Invalid state parameter", 403

    # Check for errors
    error = request.args.get("error")
    if error:
        error_description = request.args.get("error_description", "")
        return f"Authorization failed: {error} — {error_description}", 400

    # Extract the authorization code
    authorization_code = request.args.get("code")
    if not authorization_code:
        return "Missing authorization code", 400

    # Proceed to token exchange (Step 4)
    tokens = exchange_code_for_tokens(authorization_code)
    return handle_successful_linking(tokens)

Step 4: Exchange authorization code for tokens

Exchange the authorization code at Ring's token endpoint. You must include the code_verifier for PKCE validation.

Request
POST https://oauth.ring.com/oauth/token
Content-Type: application/x-www-form-urlencoded

grant_type=authorization_code
code=<authorization_code>
code_verifier=<code_verifier>
client_id=<your_client_id>
client_secret=<your_client_secret>
Parameter Required Description
grant_type Yes Always authorization_code
code Yes The authorization code received in the callback
code_verifier Yes The PKCE code verifier you stored in Step 1
client_id Yes Your Ring OAuth client ID
client_secret Yes Your Ring OAuth client secret
Response (200 OK)
{
  "access_token": "xxxxx",
  "refresh_token": "yyyyy",
  "scope": "ava.v1:read",
  "expires_in": 14400,
  "token_type": "Bearer"
}
Field Description
access_token Bearer token for Ring API calls (valid ~4 hours)
refresh_token Used to obtain new access tokens (valid ~30 days)
expires_in Access token lifetime in seconds
token_type Always Bearer
Token exchange example
import time

import requests

def exchange_code_for_tokens(authorization_code):
    """Exchange the authorization code for access and refresh tokens."""
    code_verifier = session.pop("pkce_code_verifier")

    response = requests.post(
        "https://oauth.ring.com/oauth/token",
        data={
            "grant_type": "authorization_code",
            "code": authorization_code,
            "code_verifier": code_verifier,
            "client_id": CLIENT_ID,
            "client_secret": CLIENT_SECRET,
        },
        headers={"Content-Type": "application/x-www-form-urlencoded"},
    )

    if response.status_code != 200:
        raise Exception(f"Token exchange failed: {response.text}")

    tokens = response.json()

    # Store tokens securely — associated with the authenticated partner user
    store_user_tokens(
        partner_user_id=get_current_user_id(),
        access_token=tokens["access_token"],
        refresh_token=tokens["refresh_token"],
        expires_at=time.time() + tokens["expires_in"],
    )

    return tokens

Important: The authorization code is single-use and expires within 10 minutes. Exchange it immediately after receiving it. Note that this differs from one-way account linking, where the authorization code expires after 60 seconds.

Step 5: Confirm the account link

After obtaining tokens, call the App Integrations API to finalize the account link. This step transitions the integration to a fully active state.

Request
PATCH https://api.amazonvision.com/v1/accounts/me/app-integrations
Authorization: Bearer <access_token>
Content-Type: application/json

{
  "status": "completed",
  "account_identifier": "u***[email protected]"
}
Field Type Required Description
status string Yes Set to "completed" to finalize the link. The same endpoint also accepts "awaiting" later to pause an active integration — see Pausing the integration.
account_identifier string Yes An obfuscated identifier for the partner user account (e.g., masked email). Displayed to the Ring user for confirmation.
Response (200 OK)
{
  "account_identifier": "u***[email protected]",
  "status": "completed",
  "updated_at": "2026-07-28T14:30:00Z"
}
Confirmation example
import requests

def confirm_account_link(access_token, partner_email):
    """Confirm the account link with Ring — finalizes the integration."""
    response = requests.patch(
        "https://api.amazonvision.com/v1/accounts/me/app-integrations",
        headers={
            "Authorization": f"Bearer {access_token}",
            "Content-Type": "application/json",
        },
        json={
            "status": "completed",
            "account_identifier": mask_email(partner_email),
        },
    )

    if response.status_code != 200:
        raise Exception(f"Account link confirmation failed: {response.text}")

    return response.json()


def mask_email(email):
    """Mask email for privacy: u***[email protected]"""
    local, domain = email.split("@")
    if len(local) <= 2:
        masked = local[0] + "***"
    else:
        masked = local[0] + "***" + local[-1]
    return f"{masked}@{domain}"

Important: The integration is not fully active until you call this endpoint with status: "completed". Without this call, device consents remain inactive and you cannot receive webhook notifications.

Note: Unlike one-way account linking, no POST call with a nonce is required — Partner-Initiated OAuth 2.0 uses only the PATCH call. See App Integration for the full endpoint reference.

Step 6: Start using the Ring API

After confirmation, you can call Ring APIs using the access token:

import requests

def get_devices(access_token):
    """List all Ring devices shared with your app."""
    response = requests.get(
        "https://api.amazonvision.com/v1/devices?include=status,capabilities",
        headers={
            "Authorization": f"Bearer {access_token}",
            "Content-Type": "application/json",
        },
    )
    return response.json()

For the full API reference, see API Reference.

Token Management

Token lifetimes and the refresh flow are identical to one-way account linking, with one exception — the authorization code lifetime:

Token Lifetime Purpose
Authorization code 10 minutes (single-use) Exchanged for access and refresh tokens
Access token ~4 hours (14400 seconds) Bearer token for Ring API calls
Refresh token ~30 days Used to obtain new access/refresh token pairs

Refresh tokens proactively — each refresh returns a new refresh_token that invalidates the old one, and an expired refresh token requires the user to re-link from scratch. For the complete refresh flow and best practices, see Refresh Tokens and Access Tokens.

Webhook Notifications

After account linking is complete, Ring delivers real-time event notifications to your Webhook URL. Webhook delivery, HMAC-SHA256 signature verification, payload structure, and retry logic are identical to one-way account linking — no difference in how you verify or handle them. See Notifications for full webhook documentation, including signature verification.

API polling alternative

If your architecture cannot support inbound webhooks at initial launch, you can use Ring's Event History API to poll for events:

GET https://api.amazonvision.com/v1/history/devices/{device_id}/events
Authorization: Bearer <access_token>

Webhook support via HMAC signing is available whenever you are ready to enable it.

App Discovery from Ring AppStore

Although Partner-Initiated OAuth 2.0 flows normally start from your app, your app is still listed on Ring AppStore. When a user discovers your app there and starts linking from your listing:

  • Ring forwards the user to your Account Link URL (configured in the Developer Portal)
  • Your page then starts the flow the same way it would for a user who began on your platform — redirect to Ring's authorization endpoint with the OAuth and PKCE parameters
  • This differs from one-way account linking, where the Ring AppStore runs the consent and device-selection screens itself before sending the user to your Account Link URL

This ensures a single, consistent account linking path regardless of where the user discovers your app.

Managing and Removing Integrations

Post-linking management

Device selection changes and permission review are handled by the Ring user within Ring AppStore. Partners have no API to change a user's device access or permissions. Partners can change two things about the integration itself: pause and resume it — see Pausing the integration — and remove it — see Removing the integration.

Action Who performs it How it's done
Modify device selection Ring user Ring AppStore → My Apps → select your app → change devices
Review permissions Ring user Ring AppStore → My Apps → select your app
Remove app / unlink Ring user or partner Ring user: Ring AppStore → My Apps → select your app → Remove App. Partner: DELETE /v1/accounts/me/app-integrations
Pause or resume the integration Partner PATCH /v1/accounts/me/app-integrations with status set to awaiting or completed

Partner-side UX requirement

When a user wants to change their device selection or review permissions from within your app, you must navigate them to the Ring AppStore page for your app. There is no API for changing a user's device selection, so don't attempt it from your own UI. To let the user disconnect Ring from within your app, call DELETE /v1/accounts/me/app-integrations — see Removing the integration.

Example implementation:

# When user clicks "Manage Ring Connection" in your app
RING_APPSTORE_MY_APPS_URL = "https://account.ring.com/account/integrations"

def get_manage_ring_url():
    """Return the URL where users manage their Ring integration."""
    return RING_APPSTORE_MY_APPS_URL

Pausing the integration

You can pause an active integration instead of removing it. Call the same PATCH /v1/accounts/me/app-integrations endpoint you used to activate the link, with status set to awaiting instead of completed. This moves the integration back to the awaiting state, which is useful when your service is undergoing maintenance, when you need to suspend event processing temporarily, or when your own configuration for that user is incomplete.

Pausing is reversible. Call the endpoint again with status set to completed to resume. Pausing does not invalidate tokens and does not remove the account link — the user's device authorizations and your stored credentials remain intact.

Request
PATCH https://api.amazonvision.com/v1/accounts/me/app-integrations
Authorization: Bearer <access_token>
Content-Type: application/json

{
  "status": "awaiting"
}
Value Effect
awaiting Pauses the integration
completed Resumes the integration
Example implementation
import requests

AVA_BASE_URL = "https://api.amazonvision.com"


def set_integration_status(access_token, status):
    """Pause or resume a Ring integration.

    Pass status="awaiting" to pause, status="completed" to resume.
    """
    if status not in ("awaiting", "completed"):
        raise ValueError("status must be 'awaiting' or 'completed'")

    response = requests.patch(
        f"{AVA_BASE_URL}/v1/accounts/me/app-integrations",
        headers={
            "Authorization": f"Bearer {access_token}",
            "Content-Type": "application/json",
        },
        json={"status": status},
    )

    if response.status_code != 200:
        raise Exception(f"Status update failed: {response.text}")

    return response.json()


def pause_integration(access_token):
    """Pause the integration — for example, during maintenance."""
    return set_integration_status(access_token, "awaiting")


def resume_integration(access_token):
    """Resume a paused integration."""
    return set_integration_status(access_token, "completed")

Use a valid access token. Pausing requires the access token for the specific Ring user whose integration you are pausing. Refresh the token first if it has expired.

Removing the integration

To unlink a Ring user from your app, call DELETE on the same /v1/accounts/me/app-integrations endpoint. Ring removes the integration for the Ring user identified by the Bearer token and revokes that user's access and refresh tokens. Use it when the user disconnects Ring from within your app or closes their account with you.

Removal is permanent. The user must go through the account linking flow again to reconnect. If you only need to stop event processing for a while, pause the integration instead.

Unlike POST and PATCH on this endpoint, DELETE takes no request body and no Content-Type header — the target integration comes from the Bearer token.

Availability: DELETE /v1/accounts/me/app-integrations works only for apps that use Partner-Initiated OAuth 2.0. Apps that use one-way (Ring-driven) account linking receive 403 Forbidden; their users remove the integration from Ring AppStore.

Request
DELETE https://api.amazonvision.com/v1/accounts/me/app-integrations
Authorization: Bearer <access_token>
Response (200 OK)
{
  "data": {
    "type": "app-integrations",
    "id": "ava1.ring.client.<client_id>",
    "attributes": {
      "status": "deleted"
    }
  },
  "meta": {
    "time": "2026-09-15T14:30:00Z"
  }
}

After a successful call, Ring does exactly what it does when a user removes your app from Ring AppStore: it invalidates the user's access and refresh tokens, stops sending webhook events for that user, and sends an app_integration_removed webhook — see Handling removal notifications. Discard the stored tokens as soon as the call returns 200 OK; do not wait for the webhook.

Example implementation
import requests

AVA_BASE_URL = "https://api.amazonvision.com"


def remove_integration(access_token):
    """Unlink the Ring user and revoke their tokens.

    Call this when the user disconnects Ring from your app. The access
    token must belong to the Ring user being unlinked.
    """
    response = requests.delete(
        f"{AVA_BASE_URL}/v1/accounts/me/app-integrations",
        headers={"Authorization": f"Bearer {access_token}"},
    )

    if response.status_code == 404:
        # Already removed — for example, by the user from Ring AppStore
        return None

    if response.status_code != 200:
        raise Exception(f"Integration removal failed: {response.text}")

    return response.json()["data"]["attributes"]

Use a valid access token. Removal requires the access token for the specific Ring user whose integration you are removing. Refresh the token first if it has expired.

Handling removal notifications

When a user removes your app from Ring AppStore, or your app removes the integration with DELETE, Ring:

  1. Immediately invalidates the associated access and refresh tokens
  2. Stops sending webhook events
  3. Sends an app_integration_removed webhook (if your endpoint is reachable)

Your app should handle 401 Unauthorized responses gracefully as an indicator that the user has removed the integration. Prompt the user to re-link if they want to reconnect.

Security Requirements for Partner-Initiated OAuth 2.0

PKCE (S256) — mandatory

PKCE prevents authorization code interception attacks. It is mandatory for all Partner-Initiated OAuth 2.0 flows. Ring rejects authorization requests that do not include code_challenge and code_challenge_method=S256.

Redirect URI validation

  • Ring performs exact-match validation (scheme + domain + path + port)
  • No prefix matching, wildcard matching, or partial matching
  • If the redirect_uri in the authorization request does not match a registered URI, Ring shows an error page — it does not redirect to the unvalidated URI

State parameter

  • You must include a non-empty, unpredictable state parameter in every authorization request
  • Ring echoes it back exactly in the redirect
  • Validate that the returned state matches what you stored in the user's session
  • This prevents CSRF attacks

Token storage

  • Ring does not store your (partner) user credentials
  • You are responsible for securely storing Ring access and refresh tokens
  • If tokens are lost, the user must re-link from scratch
  • Associate tokens with the specific partner user who completed the linking

Differences from One-Way Account Linking

If you are already integrated with one-way (Ring-driven) account linking, the following table shows what changes with Partner-Initiated OAuth 2.0.

Concern One-Way (Ring-Driven) Partner-Initiated OAuth 2.0
Who starts the flow Ring — user begins in Ring AppStore Partner — user begins in your app
Token Exchange URL Required — Ring POSTs the auth code to your backend Not used — you receive the code via browser redirect
Account Link URL Required — Ring redirects user here with the nonce after the token exchange Required — the page that starts the flow, and where Ring forwards users who begin from your AppStore listing
HMAC nonce matching Required — you iterate unclaimed tokens and match Not used — no nonce, no unclaimed token pool
Unclaimed token pool Required — you store tokens as "unclaimed" until nonce match Not needed — you receive and store tokens directly
App Integrations API POST (nonce verify) + PATCH (status: completed) PATCH (status: completed) + DELETE (remove the integration)
Pausing an integration PATCH with status: awaiting PATCH with status: awaiting — identical
Removing an integration Ring user only, from Ring AppStore Ring user from Ring AppStore, or partner via DELETE
Webhooks HMAC-SHA256 — identical HMAC-SHA256 — identical
Token refresh Identical Identical
Ring API usage Identical Identical

Implementation Checklist

Use this checklist to verify your integration is complete.

Developer Portal

  • App created in Ring Developer Portal
  • App allowlisted for Partner-Initiated OAuth 2.0 (Vendor ID + App ID provided to Ring)
  • OAuth Redirect URI(s) registered (HTTPS, exact-match)
  • Account Link URL configured
  • App Homepage URL configured
  • Webhook URL configured
  • Staging settings and Production settings both saved
  • App submitted for certification

Code — Account Linking

  • PKCE generation (code_verifier + code_challenge with S256)
  • Authorization redirect to account.ring.com/account/integrations/partner-link/authorize
  • state parameter generated, stored in session, and validated on callback
  • Callback handler validates state and checks for error responses
  • Token exchange at oauth.ring.com/oauth/token with code_verifier
  • Account link confirmation via PATCH /v1/accounts/me/app-integrations with status: "completed"
  • Tokens stored securely, associated with the partner user

Code — Token Management

  • Proactive token refresh before expiry (background job recommended)
  • New refresh token stored on every refresh
  • 401 Unauthorized handling — refresh and retry, or prompt re-link if refresh fails
  • Graceful handling when user removes the integration (all tokens invalidated)

Code — Webhooks

  • Webhook endpoint accepts POST with application/json
  • HMAC-SHA256 signature verification using raw body bytes
  • Idempotency handling via meta.request_id
  • Response within five seconds (async processing recommended)

User Experience

  • "Connect Ring Account" button in your app initiates the flow
  • "Manage Ring Connection" navigates the user to Ring AppStore
  • Optional — "Disconnect Ring Account" calls DELETE /v1/accounts/me/app-integrations and discards the stored tokens
  • Error states handled (user cancels, Ring base plan required, server errors)
  • Re-link prompt shown when tokens are invalidated

Error Handling Reference

Authorization errors (redirect to partner)

These errors are returned as query parameters on your redirect_uri:

error error_description Meaning Recommended action
access_denied user_cancelled User closed the flow or clicked cancel Show a friendly message; offer to retry
access_denied user_denied_consent User explicitly denied consent Inform the user that permissions are required
access_denied ring_base_plan_required No devices with an active Ring base plan Inform the user they need a Ring base plan; link to https://ring.com/plans
server_error varies Unexpected error on Ring's side Show a generic error; offer to retry later
invalid_scope — Requested scope is not approved for this app Verify your scope matches what was approved at certification

Authorization errors (shown on Ring error page — no redirect)

Condition Behavior
Invalid or unregistered redirect_uri Ring shows an error page. No redirect — unvalidated URIs are never redirected to.
Unknown client_id Ring shows an error page. No redirect.

Token exchange errors

HTTP Status Cause Fix
400 Invalid code_verifier, expired code, or code already used Ensure code_verifier matches the code_challenge from the authorization request. Authorization codes are single-use.
401 Invalid client_id or client_secret Verify your credentials.

App Integrations API errors

HTTP Status Cause Fix
400 Missing required fields or invalid status transition (PATCH); malformed request (DELETE) Ensure status is "completed" and account_identifier is present. For DELETE, send no body and no Content-Type header
403 DELETE called by an app that does not use Partner-Initiated OAuth 2.0, or the token is not authorized for this user Removal is available to Partner-Initiated OAuth 2.0 apps only. Verify the access token belongs to the Ring user you are unlinking
404 No integration found for this token Verify you are using the access token from this linking session. For DELETE, the integration is already gone — treat it as removed
500 Internal error or downstream failure. For DELETE, this includes downstream throttling — the operation never returns 429 Retry with exponential backoff

Frequently Asked Questions

Can my app support both one-way and partner-initiated account linking?

Partners can support only one account linking method per app. An app configured for Partner-Initiated OAuth 2.0 uses that method exclusively. If a user discovers the app on Ring AppStore and starts linking there, Ring forwards them to your Account Link URL, which starts the partner-initiated flow.

Do I need a Token Exchange URL?

No. The Token Exchange URL is specific to one-way (Ring-driven) account linking, where Ring POSTs the authorization code to your backend. In Partner-Initiated OAuth 2.0 you receive the authorization code on your registered OAuth Redirect URI via browser redirect, then exchange it yourself at https://oauth.ring.com/oauth/token.

Do I need to implement HMAC nonce matching?

No. The nonce mechanism is specific to one-way account linking. In Partner-Initiated OAuth 2.0, the Bearer token obtained through the code exchange is the proof of possession.

What happens if the user has no Ring base plan?

Users can only share devices covered by an active Ring base plan. If no devices qualify, Ring shows a message explaining the requirement. The flow terminates and the user must return to your app after purchasing a plan to re-initiate.

What if the user is already linked to my app?

One active link per user per app. Ring blocks duplicate linking attempts. The user must remove their existing integration from Ring AppStore before re-linking.

Can I programmatically unlink a user?

Yes. Call DELETE /v1/accounts/me/app-integrations with the user's access token. Ring removes the integration, revokes the user's tokens, and sends an app_integration_removed webhook — see Removing the integration. This works for Partner-Initiated OAuth 2.0 apps only. If you only need to stop event processing temporarily, pause the integration instead.

How do I pause an integration without unlinking the user?

Call PATCH /v1/accounts/me/app-integrations with status set to awaiting. Resume by calling it again with status set to completed. Pausing keeps tokens and device authorizations intact — see Pausing the integration.

What are the token lifetimes?

Access token: ~4 hours. Refresh token: ~30 days. You must refresh tokens proactively — an expired refresh token requires the user to re-link.

Do webhooks work the same way?

Yes. Webhook delivery, HMAC-SHA256 signature verification, payload structure, and retry logic are identical regardless of which account linking method was used. See Notifications.

Related Documentation

  • API Reference — Full endpoint reference
  • Authentication — OAuth overview and token types
  • Access Tokens — Token usage and management
  • Refresh Tokens — Token refresh flow
  • Notifications — Webhook events and HMAC verification
  • App Integration — App Integrations API reference
  • Device Discovery — Listing and querying devices
  • One-Way Account Linking — The Ring-driven account linking method
  • Getting Started — App creation and onboarding

Refresh Tokens

Refresh tokens enable partners to maintain long-term access to Ring user data by obtaining new access tokens when the current ones expire.

Overview

  • Access tokens have limited validity (typically 4 hours / 14400 seconds)
  • Refresh tokens are valid for approximately 30 days
  • Partners must refresh tokens proactively before they expire — an expired refresh token cannot be recovered and requires the Ring user to re-link their account through the Ring App
  • Partners should store refresh tokens securely for continuous access
Warning: Refresh tokens cannot be used directly in Authorization: Bearer headers. You MUST exchange them for an access token first using the POST https://oauth.ring.com/oauth/token endpoint described below. Placing a refresh token in the Authorization: Bearer header will result in 401 Unauthorized errors.

Quick Start: Exchanging a Refresh Token

HTTP Request

POST https://oauth.ring.com/oauth/token
Content-Type: application/x-www-form-urlencoded

grant_type=refresh_token&refresh_token=<your_refresh_token>&client_id=<your_client_id>&client_secret=<your_client_secret>

Python Example

import requests

OAUTH_TOKEN_URL = "https://oauth.ring.com/oauth/token"

def exchange_refresh_token(refresh_token, client_id, client_secret):
    """Exchange a refresh token for a new access token."""
    response = requests.post(OAUTH_TOKEN_URL, data={
        "grant_type": "refresh_token",
        "refresh_token": refresh_token,
        "client_id": client_id,
        "client_secret": client_secret,
    })
    response.raise_for_status()
    tokens = response.json()

    # tokens contains: access_token, refresh_token, expires_in, token_type, scope
    return tokens

# Usage

tokens = exchange_refresh_token(
    refresh_token="your_refresh_token_here",
    client_id="your_client_id_here",
    client_secret="your_client_secret_here",
)

# Now use the access token for API calls
headers = {"Authorization": f"Bearer {tokens['access_token']}"}

JavaScript Example

async function exchangeRefreshToken(refreshToken, clientId, clientSecret) {
  const response = await fetch("https://oauth.ring.com/oauth/token", {
    method: "POST",
    headers: { "Content-Type": "application/x-www-form-urlencoded" },
    body: new URLSearchParams({
      grant_type: "refresh_token",
      refresh_token: refreshToken,
      client_id: clientId,
      client_secret: clientSecret,
    }),
  });

  if (!response.ok) throw new Error(`Token exchange failed: ${response.status}`);
  return await response.json();
}

When to Refresh

Ring recommends refreshing tokens in these scenarios:

  1. Proactive refresh: Based on the expires_in value from the token response
  2. Reactive refresh: When receiving HTTP 401 responses from Ring endpoints

Refresh Token Exchange

POST https://oauth.ring.com/oauth/token
Content-Type: application/x-www-form-urlencoded

grant_type=refresh_token
refresh_token=<refresh_token>
client_id=<client_id>
client_secret=<client_secret>

Response:

{
  "access_token": "xxxxx",
  "refresh_token": "yyyyy",
  "scope": "<scope>",
  "expires_in": 14400,
  "token_type": "Bearer"
}

Implementation Best Practices

Token Storage

  • Store refresh tokens securely (encrypted at rest)
  • Associate tokens with the correct user accounts
  • Implement proper access controls

Proactive Refresh Strategy

To prevent refresh token expiry, implement a background job that refreshes tokens before they expire:

import time

# Recommended: refresh tokens every 24 hours to maintain a healthy margin
REFRESH_INTERVAL_SECONDS = 86400  # 24 hours

def background_token_refresh():
    """Background job to proactively refresh all stored tokens."""
    for token_record in get_all_active_tokens():
        try:
            new_tokens = refresh_access_token(token_record.refresh_token)
            update_stored_tokens(token_record.account_id, new_tokens)
        except Exception as e:
            # Alert on refresh failures — may indicate approaching expiry
            alert_token_refresh_failure(token_record.account_id, e)
Warning: CRITICAL: If a refresh token expires (after ~30 days of inactivity), the partner loses access to that Ring user's data. The only recovery path is for the Ring user to re-link their account through the Ring App. Partners should implement monitoring to detect tokens approaching expiry and refresh them well in advance.

Security Considerations

  • Refresh tokens are sensitive credentials
  • Each refresh returns a new refresh_token and invalidates the old one. Store the new token every time you refresh
  • Monitor for tokens approaching the 30-day expiry window
  • Implement alerting for failed refresh attempts
  • Implement rate limiting to prevent abuse

Related Documentation

  • Access Tokens — How to use access tokens in API requests
  • One-Way Account Linking — How the initial token exchange works

Access Tokens

Access tokens are the primary authentication material used to access Ring user data. They are user-scoped, meaning each token is associated with a specific Ring user account.

Token Characteristics

  • Short-lived: Typically valid for 4 hours (14400 seconds)
  • User-scoped: Each token provides access to one user's data
  • Bearer tokens: Used in HTTP Authorization headers
Warning: Only access tokens can be used in Authorization: Bearer headers. Refresh tokens are not valid Bearer tokens — using a refresh token in the Authorization header will result in 401 Unauthorized errors. If you only have a refresh token, you must first exchange it for an access token via POST https://oauth.ring.com/oauth/token. See Refresh Tokens for the exchange flow.

Usage

Include access tokens in the Authorization header for all API requests:

Authorization: Bearer <access_token>

Example API Request

GET https://api.amazonvision.com/v1/devices
Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...

Token Management

Expiration Handling

Access tokens include an expires_in field indicating their lifetime in seconds:

{
  "access_token": "xxxxx",
  "refresh_token": "yyyyy",
  "scope": "<scope>",
  "expires_in": 14400,
  "token_type": "Bearer"
}

Implementation Example

import time
import requests

class TokenManager:
    def __init__(self, access_token, refresh_token, expires_in):
        self.access_token = access_token
        self.refresh_token = refresh_token
        self.expires_at = time.time() + expires_in
    
    def get_valid_token(self):
        if time.time() >= self.expires_at - 300:  # Refresh 5 minutes early
            self.refresh_token_if_needed()
        return self.access_token
    
    def refresh_token_if_needed(self):
        # Implement refresh logic here
        pass
    
    def make_authenticated_request(self, url):
        headers = {
            'Authorization': f'Bearer {self.get_valid_token()}'
        }
        return requests.get(url, headers=headers)

Best Practices

  1. Track expiration: Monitor token expiration times
  2. Proactive refresh: Refresh tokens before they expire
  3. Handle 401 responses: Implement automatic token refresh on authentication failures
  4. Secure storage: Store tokens securely and associate with correct users

Account ID Retrieval

After receiving an access token, partners should call GET https://api.amazonvision.com/v1/users/me to retrieve the Ring user's Account ID. This Account ID is used for nonce matching during account linking and correlating webhook events. See Users API for details.

Scope and Permissions

Access tokens are scoped to specific permissions granted during the account linking process. The scope determines which Ring resources and operations the token can access.

Common scopes include:

  • Device discovery and status
  • Video streaming access
  • Notification subscriptions
  • Configuration reading

Security Considerations

  • Never log or expose access tokens
  • Use HTTPS for all API requests
  • Implement proper token rotation
  • Monitor for suspicious usage patterns
  • Revoke tokens when users disconnect integrations

Related Documentation

  • Refresh Tokens — How to refresh expired access tokens
  • One-Way Account Linking — How the initial token exchange works

Users API

The Users API allows partners to retrieve basic profile information about the Ring user associated with their AVA access token. This endpoint returns the user's Account ID, name, email, and phone number.

Overview

Method Endpoint Purpose
GET https://api.amazonvision.com/v1/users/me Get the authenticated user's profile information

Authentication

Requires a valid AVA access token:

Authorization: Bearer <ava_token>

See Access Tokens for details on obtaining and managing tokens.


GET — User Profile

Returns basic profile information for the Ring user associated with the provided AVA token. The me path parameter indicates the currently authenticated user.

Request

GET https://api.amazonvision.com/v1/users/me
Authorization: Bearer <ava_token>

No request body is required.

Response (200 OK)

{
  "data": {
    "type": "users",
    "id": "ava1.ring.account.XXXYYY",
    "attributes": {
      "first_name": "John",
      "last_name": "Doe",
      "email": "[email protected]"
    }
  }
}

Response Fields

Field Type Description
data.type string Always users
data.id string The user's Account ID — use this to identify the Ring user in your system
data.attributes.first_name string User's first name
data.attributes.last_name string User's last name
data.attributes.email string User's email address
data.attributes.phone_number string User's phone number

Error Responses

Status Description
400 Malformed request
403 Client is not authenticated or token is invalid
404 No user found for the given token
500 Internal server error

Key Uses of Account ID

The Account ID (data.id) returned by this endpoint is used throughout the Ring integration:

  1. Nonce matching during account linking — The Account ID is the binding identifier in the HMAC nonce. Ring computes the nonce as HMAC-SHA256(K_hmac, "<timestamp_ms>:<account_id>"). Partners call this endpoint for each unclaimed token at receipt time to store the Account ID, then use it to recompute and match the nonce during account linking. See One-Way Account Linking for details.

  2. Webhook event association — The same Account ID appears in the meta.account_id field of all webhook notifications, allowing partners to associate events with the correct Ring user. See Notifications.

  3. User management — Use the Account ID as the stable identifier for Ring users in your system. Avoid indexing on email or name as these may change.


Implementation Example

Basic Profile Retrieval

import requests

def get_user_profile(ava_token):
    """Get the Ring user profile for the given AVA token."""
    response = requests.get(
        "https://api.amazonvision.com/v1/users/me",
        headers={
            "Authorization": f"Bearer {ava_token}"
        }
    )

    if response.status_code == 200:
        user_data = response.json()['data']
        return {
            "account_id": user_data['id'],
            "first_name": user_data['attributes']['first_name'],
            "last_name": user_data['attributes']['last_name'],
            "email": user_data['attributes']['email'],
            "phone_number": user_data['attributes']['phone_number']
        }
    elif response.status_code == 403:
        raise PermissionError("Invalid or expired token")
    elif response.status_code == 404:
        raise LookupError("User not found")
    else:
        response.raise_for_status()

Store Account ID at Token Receipt

Call this endpoint immediately after receiving an AVA token to store the Account ID for later use in nonce matching and event correlation:

def on_token_received(ava_token, refresh_token):
    """Called when a new AVA token is received during OAuth exchange."""
    profile = get_user_profile(ava_token)
    account_id = profile['account_id']

    token_store.save(
        access_token=ava_token,
        refresh_token=refresh_token,
        account_id=account_id,
        status="unclaimed"
    )

    return account_id

Best Practices

  1. Call immediately after token exchange — Retrieve the Account ID as soon as you receive the AVA token so it's available for nonce matching
  2. Use Account ID as primary identifier — Do not index on email or name, as users can change these at any time
  3. Cache profile data carefully — User profile data may change; re-fetch periodically if displaying user info
  4. Handle token expiration — If you get a 403, refresh the token and retry
  5. Privacy compliance — Handle user personal information according to your privacy policy and Ring's partner agreements

App Integration

The App Integrations API allows partners to manage the lifecycle of their Ring AppStore integration. Partners use these endpoints to confirm account linking, update integration configuration status, and manage the association between their users and Ring users.

Overview

The App Integrations API provides two operations on a single endpoint. Both calls are required to complete the integration — POST to confirm the account link, then PATCH to finalize the integration status:

Order Method Endpoint Purpose
1 POST https://api.amazonvision.com/v1/accounts/me/app-integrations Confirm account link — validate the nonce and associate a partner account with a Ring user (status → awaiting)
2 PATCH https://api.amazonvision.com/v1/accounts/me/app-integrations Required — Update integration status to completed after partner-side configuration is done
Important: Calling PATCH after POST is mandatory. The integration is not complete until the partner calls PATCH with status: completed. Without this call, the integration remains in awaiting state and is not fully operational in the Ring AppStore.

Both endpoints:

  • Require a valid AVA access token in the Authorization: Bearer header
  • Derive the Ring user identity from the token (no explicit user ID needed)
  • Accept standard JSON request bodies (Content-Type: application/json)
  • Return JSON:API formatted responses — the resource is wrapped in a data object alongside a meta object

Authentication

All requests require a valid AVA access token obtained through the OAuth token exchange flow:

Authorization: Bearer <ava_token>

See Access Tokens for details.


POST — Confirm Account Link

Called by the Partner App Backend after a Ring user has authenticated with the partner service. This endpoint verifies the nonce from the account linking redirect and establishes the link between the Ring user and the partner account.

When to Call

Call this endpoint after:

  1. The Ring user has been redirected to your Account Link URL with time and nonce query parameters
  2. The user has signed in to your partner service (mandatory)
  3. You have matched the nonce to the correct unclaimed AVA token

Request

POST https://api.amazonvision.com/v1/accounts/me/app-integrations
Authorization: Bearer <ava_token>
Content-Type: application/json

{
  "account_identifier": "u***[email protected]",
  "nonce": "<nonce_value>"
}

Request Fields

Field Type Required Description
account_identifier string Optional Obfuscated partner account identifier displayed to the Ring user for confirmation (e.g., masked email u***[email protected]). Must be derived from the user's actual signed-in session.
nonce string Required The nonce received from the Ring redirect URL — used to cryptographically verify the linking attempt

Response (200 OK)

{
  "data": {
    "type": "app-integrations",
    "id": "ava1.ring.client.<client_id>",
    "attributes": {
      "status": "awaiting"
    }
  },
  "meta": {
    "time": "2026-07-21T07:07:15Z"
  }
}

Response Fields

Field Type Description
data.type string Always app-integrations
data.id string Opaque client identifier, prefixed ava1.ring.client. — treat it as a string key
data.attributes.status string Integration status after verification — awaiting indicates the link is verified and awaiting configuration completion
meta.time string (ISO 8601) Timestamp of the response

Error Responses

Status Title Description
400 Invalid Nonce Nonce validation failed
400 Missing Required Fields The nonce field is missing
404 Not Found No integration exists for this token

Side Effects

When this endpoint is called successfully:

  • The integration status transitions to awaiting
  • Device-level consents are activated — the partner can now access authorized devices
  • Ring sends a confirmation email to the Ring user
  • Ring sends an app_integration_added webhook and one device_added webhook for each shared device, typically within seconds. They can arrive before this call returns.

Implementation Example

import requests

def verify_account_link(ava_token, account_identifier, nonce):
    """Verify the account link with Ring after user authenticates."""
    response = requests.post(
        "https://api.amazonvision.com/v1/accounts/me/app-integrations",
        headers={
            "Authorization": f"Bearer {ava_token}",
            "Content-Type": "application/json"
        },
        json={
            "account_identifier": account_identifier,
            "nonce": nonce
        }
    )

    if response.status_code == 200:
        # Status is nested inside the JSON:API envelope
        return response.json()["data"]["attributes"]["status"]  # "awaiting"
    elif response.status_code == 400:
        raise ValueError(f"Verification failed: {response.json()}")
    elif response.status_code == 404:
        raise LookupError("Integration not found for this token")
    else:
        response.raise_for_status()

PATCH — Update Integration Status

Called by the Partner App Backend to update the integration status. This call is mandatory after the POST nonce validation to finalize the integration.

When to Call

  • During initial integration (required): After POST succeeds — call PATCH with status: completed
  • During ongoing operations (as needed): To pause configuration or update account_identifier

Request

PATCH https://api.amazonvision.com/v1/accounts/me/app-integrations
Authorization: Bearer <ava_token>
Content-Type: application/json

{
  "account_identifier": "u***[email protected]",
  "status": "completed"
}

Request Fields

Field Type Required Description
account_identifier string Optional Updated partner account identifier
status string Optional completed (configuration done) or awaiting (in progress)

At least one field must be provided.

Response (200 OK)

{
  "data": {
    "type": "app-integrations",
    "id": "ava1.ring.client.<client_id>",
    "attributes": {
      "account_identifier": "u***[email protected]",
      "status": "completed",
      "updated_at": "2026-07-21T07:07:16Z"
    }
  },
  "meta": {
    "time": "2026-07-21T07:07:16Z"
  }
}

Response Fields

Field Type Description
data.type string Always app-integrations
data.id string Opaque client identifier, prefixed ava1.ring.client. — treat it as a string key
data.attributes.account_identifier string The current partner account identifier, returned in the same masked form it was submitted
data.attributes.status string The current integration status — awaiting or completed
data.attributes.updated_at string (ISO 8601) Timestamp of this update
meta.time string (ISO 8601) Timestamp of the response

Error Responses

Status Title Description
400 Missing Required Fields Request body is empty
400 Invalid Status Transition The requested status change is not allowed
404 Not Found No integration exists for this token

Integration Status Lifecycle

Status Meaning Set By Required
awaiting Link verified, device access enabled, awaiting partner configuration Ring (after POST) or Partner (via PATCH) POST triggers this automatically
completed Fully configured and operational Partner (via PATCH) PATCH is mandatory to reach this state

Required integration flow:

  1. POST with nonce → status becomes awaiting
  2. PATCH with status: completed → status becomes completed (mandatory)

Complete Integration Example

import requests

def complete_integration(ava_token, account_identifier, nonce):
    """Complete the full integration: POST nonce verification, then PATCH to completed."""

    # Step 1: POST — Verify the nonce and confirm account link
    post_response = requests.post(
        "https://api.amazonvision.com/v1/accounts/me/app-integrations",
        headers={
            "Authorization": f"Bearer {ava_token}",
            "Content-Type": "application/json"
        },
        json={
            "account_identifier": account_identifier,
            "nonce": nonce
        }
    )

    if post_response.status_code != 200:
        raise ValueError(f"POST nonce verification failed: {post_response.json()}")

    # Step 2: PATCH — Update status to completed (REQUIRED)
    patch_response = requests.patch(
        "https://api.amazonvision.com/v1/accounts/me/app-integrations",
        headers={
            "Authorization": f"Bearer {ava_token}",
            "Content-Type": "application/json"
        },
        json={
            "status": "completed"
        }
    )

    if patch_response.status_code != 200:
        raise ValueError(f"PATCH status update failed: {patch_response.json()}")

    # Attributes are nested inside the JSON:API envelope
    return patch_response.json()["data"]["attributes"]

Best Practices

  1. Call POST immediately after nonce matching — The verification window is time-limited
  2. Call PATCH after POST succeeds — Mandatory to finalize the integration
  3. Mask account identifiers — Use obfuscated identifiers to protect user privacy
  4. Update account_identifier on changes — If the user updates their email, call PATCH
  5. Handle errors gracefully — Implement retry logic for transient failures
  6. Use the correct AVA token — Must be associated with the specific Ring user being linked
  7. Require user sign-in before processing the nonce — The account_identifier must come from an authenticated partner session

Device Discovery

After obtaining customer authentication materials, partners can access Ring's public API to discover and interact with shared devices. Device discovery is the entry point for accessing Ring resources.

Overview

Device discovery follows the JSON:API Specification and provides:

  • Basic device information and identification
  • Device capabilities and supported features
  • Current device status and connectivity
  • Location information (coarse-grained)
  • User configurations and settings

Basic Device Discovery

Retrieve a list of devices accessible to the authenticated user:

GET https://api.amazonvision.com/v1/devices
Authorization: Bearer <access_token>

Response Structure

{
  "meta": {
    "time": "2026-02-14T22:57:16Z"
  },
  "data": [
    {
      "type": "devices",
      "id": "ava1.ring.device.XXXYYY",
      "attributes": {
        "name": "Front Door Camera"
      },
      "relationships": {
        "status": {
          "data": {
            "type": "device-status",
            "id": "ava1.ring.device.status.XXXYYY"
          },
          "links": {
            "related": "/v1/devices/ava1.ring.device.XXXYYY/status"
          }
        },
        "capabilities": {
          "data": {
            "type": "device-capabilities",
            "id": "ava1.ring.device.capabilities.XXXYYY"
          },
          "links": {
            "related": "/v1/devices/ava1.ring.device.XXXYYY/capabilities"
          }
        },
        "configurations": {
          "data": {
            "type": "device-configurations",
            "id": "ava1.ring.device.configurations.XXXYYY"
          },
          "links": {
            "related": "/v1/devices/ava1.ring.device.XXXYYY/configurations"
          }
        },
        "location": {
          "data": {
            "type": "locations",
            "id": "ava1.ring.device.location.XXXYYY"
          },
          "links": {
            "related": "/v1/devices/ava1.ring.device.XXXYYY/location"
          }
        }
      }
    }
  ]
}

Response Fields

Field Type Description
meta.time string ISO 8601 timestamp of the response
data array Array of device resources
data[].type string Always "devices"
data[].id string Unique device identifier (e.g., ava1.ring.device.XXXYYY) — use this in all device-specific API calls
data[].attributes.name string User-assigned device name (e.g., "Front Door Camera")
data[].attributes.image_url string URL of a square product image for the device
data[].relationships object JSON:API resource linkage — each relationship contains data (type + id) and links (API path)
Note: Device IDs are opaque strings (e.g., ava1.ring.device.GPBGLFYKSWL2...). Do not parse or make assumptions about their structure — use them as-is in API paths.

Including Related Data

Use the include query parameter to fetch related data in a single request:

GET https://api.amazonvision.com/v1/devices?include=status,capabilities
Authorization: Bearer <access_token>

Response with Included Data (Compound Document)

When using ?include=status,capabilities, the related resources are returned in a top-level included array. Match resources using the type + id pair from relationships.{rel}.data to the corresponding entry in included:

{
  "meta": {
    "time": "2026-02-14T22:57:16Z"
  },
  "data": [
    {
      "type": "devices",
      "id": "ava1.ring.device.XXXYYY",
      "attributes": {
        "name": "Office"
      },
      "relationships": {
        "status": {
          "data": { "type": "device-status", "id": "ava1.ring.device.status.XXXYYY" },
          "links": { "related": "/v1/devices/ava1.ring.device.XXXYYY/status" }
        },
        "capabilities": {
          "data": { "type": "device-capabilities", "id": "ava1.ring.device.capabilities.XXXYYY" },
          "links": { "related": "/v1/devices/ava1.ring.device.XXXYYY/capabilities" }
        }
      }
    }
  ],
  "included": [
    {
      "type": "device-status",
      "id": "ava1.ring.device.status.XXXYYY",
      "attributes": {
        "online": true
      }
    },
    {
      "type": "device-capabilities",
      "id": "ava1.ring.device.capabilities.XXXYYY",
      "attributes": {
        "video": {
          "configurations": ["resolution_mode"],
          "codecs": ["AVC"],
          "ratio": "16:9",
          "max_resolution": 1080,
          "supported_resolutions": [1080]
        },
        "motion_detection": {
          "configurations": ["enabled", "motion_zones"]
        },
        "image_enhancements": {
          "configurations": ["color_night_vision", "hdr", "ir_led_night_vision", "privacy_zones"]
        }
      }
    }
  ]
}
Note: How ?include= works: Resources requested via include appear in the top-level included array. Resources NOT requested still appear in relationships with data and links but are not embedded in included — use the links.related path to fetch them individually. You can include any combination: status, capabilities, location, configurations.

Included Resource Fields

Included Type Key Attributes Description
device-status online (boolean) Whether the device is currently online
device-capabilities button_press, video, motion_detection, image_enhancements, components Device feature support — doorbell press support, codecs, resolutions, configurable settings. button_press appears on doorbells only, and components on multi-camera devices only. See Capabilities
device-configurations motion_detection, image_enhancements Current user settings — zones, enhancements. See Configurations
locations Coarse location data Country/state-level location. See Locations

Devices with multiple cameras

A Ring Elite camera contains several camera modules but returns a single device entry, structurally identical to any other camera. Sideload capabilities with ?include=capabilities and check for a components object in the device-capabilities resource: its presence tells you the device has multiple camera modules and lists them. Devices without components are single-camera devices.

For the full model, see Multi-Camera Devices.

Device Relationships

Each device provides relationships to:

  • Status: Online/offline state and connectivity — see Status
  • Capabilities: Supported features and hardware specs — see Capabilities
  • Location: Coarse geographic information — see Locations
  • Configurations: User settings and preferences — see Configurations

JSON:API Compliance

The device discovery API follows JSON:API specifications:

  • Consistent resource structure with type, id, attributes, relationships
  • Resource linkage via data objects (type + id) in relationships
  • Compound documents via include parameter with top-level included array
  • Relationship links for individual resource fetching
  • Extensible attribute model for future enhancements

Rate Limiting

  • Respect rate limits (don't exceed 100 TPS)
  • Implement exponential backoff for retries
  • Cache device information when appropriate

Sub-pages

  • Capabilities — Device hardware features and supported functionality
  • Status — Real-time device availability and connectivity
  • Locations — Coarse geographic information for compliance
  • Configurations — User settings, motion zones, and privacy zones
  • Multi-Camera Devices — Addressing individual cameras on Ring Elite devices

Capabilities

Device capabilities provide information about the hardware features and supported functionality of Ring devices. This information helps partners understand what operations are available for each device.

Accessing Capabilities

Capabilities can be retrieved in two ways:

  1. During device discovery using the include parameter:
    GET https://api.amazonvision.com/v1/devices?include=capabilities
    Authorization: Bearer <access_token>
    
  2. Individual device capabilities:
    GET https://api.amazonvision.com/v1/devices/{device_id}/capabilities
    Authorization: Bearer <access_token>
    
  3. A single camera module on a multi-camera device, using the component_id query parameter:
    GET https://api.amazonvision.com/v1/devices/{device_id}/capabilities?component_id=5
    Authorization: Bearer <access_token>
    

Response Structure

{
  "meta": {
    "time": "2025-07-07T10:30:00Z"
  },
  "data": {
    "id": "xxxyyy.capabilities",
    "type": "device-capabilities",
    "attributes": {
      "button_press": {
        "supported": true
      },
      "video": {
        "configurations": ["resolution_mode"],
        "codecs": ["HEVC"],
        "ratio": "16:9",
        "max_resolution": 2160,
        "supported_resolutions": [2160]
      },
      "motion_detection": {
        "configurations": ["enabled", "motion_zones"]
      },
      "image_enhancements": {
        "configurations": [
          "color_night_vision",
          "hdr",
          "ir_led_night_vision",
          "auto_zoom_track",
          "privacy_zones"
        ]
      }
    },
    "relationships": {
      "configurations": {
        "links": {
          "related": "/v1/devices/xxxyyy/configurations"
        }
      }
    }
  }
}

Capability Types

Button Press

  • supported: true when the device is a doorbell and sends Button Press events

Doorbells return the button_press object. Other devices don't, so treat a missing button_press object as no doorbell button. Read it at discovery time to choose the experience you offer for a device before any event arrives.

Video Capabilities

  • codecs: Supported video codecs (HEVC, AVC)
  • ratio: Video aspect ratio
  • max_resolution: Maximum supported resolution
  • supported_resolutions: Array of available resolutions
  • configurations: Related configuration options
  • component_id: On a multi-camera device, the camera module this block describes
Note: Read ratio and max_resolution — don't assume them. Both vary by device, and the values in the example above are not universal. A Ring Elite 360 reports a ratio of 19:21 and a max_resolution of 2736. Marketing resolution claims don't map directly to these values either, so always use what the response gives you.

Components

Devices with more than one camera module — the Ring Elite lineup — list them under components. The field is absent on single-camera devices.

Field Type Description
components.items[].component_id string Identifier for one camera module. Pass as the component_id query parameter.
components.items[].component_type string Module type — "lens" for a camera module.
components.items[].component_name string Display label, such as "View 1".

The array order matches the physical arrangement of the modules. See Multi-Camera Devices.

Motion Detection Capability

  • configurations: Available motion detection settings
  • Typically includes motion zones and enable/disable options

Image Enhancements

  • color_night_vision: Color night vision support
  • hdr: High Dynamic Range support
  • ir_led_night_vision: Infrared LED night vision
  • auto_zoom_track: Automatic zoom and tracking
  • privacy_zones: Privacy zone configuration

Per-component capabilities

Request one camera module's metadata with the component_id query parameter.

GET https://api.amazonvision.com/v1/devices/{device_id}/capabilities?component_id=5
Authorization: Bearer <access_token>
{
  "data": {
    "type": "component-capabilities",
    "id": "ava1.ring.component.capabilities.XXXYYY",
    "attributes": {
      "component_type": "lens",
      "component_id": "5",
      "component_name": "View 6"
    },
    "relationships": {
      "configurations": {
        "links": {
          "related": "/v1/devices/{device_id}/configurations?component_id=5"
        }
      }
    }
  },
  "meta": {
    "time": "2026-08-14T13:42:45Z"
  }
}

The scoped response returns identity metadata only — component_type, component_id, and component_name. Video, motion detection, and image enhancement capabilities aren't scoped per component. Read those from the device-level response, which describes the first component in components.items.

Note the resource type is component-capabilities (not device-capabilities) and the id uses the ava1.ring.component.capabilities. prefix.

Important Notes

  • Optional capabilities: Not all devices support all capabilities
  • Device variation: Capabilities vary significantly between Ring device models
  • Configuration relationship: Each capability lists its related configuration options
  • Future extensibility: Additional capabilities may be added over time

Using Capability Information

Partners should:

  1. Check for capability existence before attempting related operations
  2. Use the configurations array to understand available settings
  3. Handle missing capabilities gracefully
  4. Cache capability information as it rarely changes

Example Usage

def check_video_capabilities(device_capabilities):
    video_caps = device_capabilities.get('attributes', {}).get('video')
    if video_caps:
        max_res = video_caps.get('max_resolution', 1080)
        codecs = video_caps.get('codecs', ['AVC'])
        return {
            'supports_4k': max_res >= 2160,
            'supports_hevc': 'HEVC' in codecs
        }
    return {'supports_4k': False, 'supports_hevc': False}

def supports_doorbell_press(device_capabilities):
    button_press = device_capabilities.get('attributes', {}).get('button_press', {})
    return button_press.get('supported') is True

Status

Device status provides real-time information about device availability and connectivity. This is essential for determining whether devices can respond to requests or stream video.

Accessing Device Status

Status information can be retrieved in two ways:

  1. During device discovery using the include parameter:
    GET https://api.amazonvision.com/v1/devices?include=status
    Authorization: Bearer <access_token>
    
  2. Individual device status:
    GET https://api.amazonvision.com/v1/devices/{device_id}/status
    Authorization: Bearer <access_token>
    

Response Structure

{
  "meta": {
    "time": "2025-07-07T10:30:00Z"
  },
  "data": {
    "type": "device-status",
    "id": "xxxyyy.status",
    "attributes": {
      "online": true
    }
  }
}

Status Attributes

Online Status

  • online: Boolean indicating if the device is currently connected
  • Primary indicator for device availability
  • Essential for determining if live streaming or commands will work

Usage Patterns

Checking Device Availability

def is_device_available(device_status):
    return device_status.get('attributes', {}).get('online', False)

def can_stream_video(device_id, device_status):
    if is_device_available(device_status):
        return True
    else:
        print(f"Device {device_id} is offline - streaming not available")
        return False

Best Practices

  1. Check before operations: Always verify device is online before attempting video streaming or configuration changes
  2. Handle offline gracefully: Provide appropriate user feedback when devices are offline
  3. Cache appropriately: Status can change frequently, so don't cache for extended periods
  4. Monitor status changes: Consider implementing status monitoring for critical operations

Error Scenarios

When devices are offline:

  • Live video streaming will fail
  • Configuration changes may not be applied immediately
  • Motion detection may not trigger notifications
  • Historical video may still be available depending on cloud storage

Locations

Device location information provides coarse, non-specific geographic data about where Ring devices are installed. This information may be required for determining local legal compliance or regional feature availability.

Accessing Location Data

Location information can be retrieved in two ways:

  1. During device discovery using the include parameter:
    GET https://api.amazonvision.com/v1/devices?include=location
    Authorization: Bearer <access_token>
    
  2. Individual device location:
    GET https://api.amazonvision.com/v1/devices/{device_id}/location
    Authorization: Bearer <access_token>
    

Response Structure

{
  "meta": {
    "time": "2025-07-07T10:30:00Z"
  },
  "data": {
    "type": "locations",
    "id": "zzzzzz",
    "attributes": {
      "country": "US",
      "state": "GA"
    }
  }
}

Location Attributes

Country

  • country: ISO country code (e.g., "US", "CA", "GB")
  • Always provided when location data is available
  • Primary identifier for regional compliance

State/Province

  • state: State or province code (e.g., "GA", "CA", "ON")
  • Provided for devices in the United States when available
  • Should be treated as optional and non-guaranteed
  • May not be available for all countries

Privacy and Granularity

Ring provides intentionally coarse location information to protect user privacy:

  • No precise coordinates: Exact addresses or GPS coordinates are never provided
  • Country-level minimum: At minimum, country information is provided
  • State-level maximum: For supported regions, state/province may be included
  • No city-level data: Specific cities or postal codes are not provided

Use Cases

Legal Compliance

def check_regional_compliance(device_location):
    country = device_location.get('attributes', {}).get('country')
    state = device_location.get('attributes', {}).get('state')
    
    if country == 'US':
        if state in ['CA', 'IL']:  # States with specific privacy laws
            return apply_enhanced_privacy_controls()
    elif country in ['GB', 'DE', 'FR']:
        return apply_gdpr_controls()
    
    return apply_default_controls()

Important Considerations

  1. Optional data: Location information may not be available for all devices
  2. Privacy first: Location data is intentionally limited to protect user privacy
  3. Compliance focus: Primary use case is legal and regulatory compliance
  4. No tracking: This data should not be used for user tracking or analytics
  5. Static nature: Device locations typically don't change frequently

Configurations

Device configurations provide details about user-specified settings that may affect partner processing of video content and device behavior. These settings are particularly important for motion detection and privacy considerations.

Accessing Configuration Data

Configuration information can be retrieved in two ways:

  1. During device discovery using the include parameter:
    GET https://api.amazonvision.com/v1/devices?include=configurations
    Authorization: Bearer <access_token>
    
  2. Individual device configurations:
    GET https://api.amazonvision.com/v1/devices/{device_id}/configurations
    Authorization: Bearer <access_token>
    
  3. A single camera module on a multi-camera device, using the component_id query parameter:
    GET https://api.amazonvision.com/v1/devices/{device_id}/configurations?component_id=5
    Authorization: Bearer <access_token>
    

Response Structure

{
  "meta": {
    "time": "2026-02-14T23:37:48Z"
  },
  "data": {
    "type": "device-configurations",
    "id": "ava1.ring.device.configurations.XXXYYY",
    "attributes": {
      "e2e_encryption": {
        "enabled": false
      },
      "motion_detection": {
        "enabled": "on",
        "motion_zones": [
          {
            "id": "a9d225a6-3122-4f4d-a933-0c24e53cc535",
            "vertices": [
              {"x": 0, "y": 0.2},
              {"x": 0.5, "y": 0.2},
              {"x": 1, "y": 0.2},
              {"x": 1, "y": 0.6},
              {"x": 1, "y": 1},
              {"x": 0.5, "y": 1},
              {"x": 0, "y": 1},
              {"x": 0, "y": 0.6}
            ]
          }
        ],
        "component_id": "0"
      },
      "video": {
        "component_id": "0"
      },
      "image_enhancements": {
        "color_night_vision": "off",
        "hdr": "off",
        "ir_led_night_vision": "off",
        "auto_zoom_track": "off",
        "privacy_zones": []
      }
    }
  }
}
Note: Privacy zones use the same structure as motion zones (array of objects with id and vertices). When no privacy zones are configured, the array is empty ([]).

Configuration Categories

End-to-End Encryption

  • enabled: Whether video encryption is turned on for this device (true or false). When true, video requests for the device still succeed but return encrypted content your app cannot read
Important: e2e_encryption.enabled is a JSON boolean, not one of the on/off strings the rest of this response uses — motion_detection.enabled in the same payload is the string on. Compare e2e_encryption.enabled to true, not to a string. Read it before you attempt any video processing for a device, and treat unreadable media as an expected outcome for a subset of devices rather than retrying or surfacing an error. See Video encryption (TAKE and E2EE).

Motion Detection Configuration

  • enabled: Motion detection on/off status ("on" or "off")
  • motion_zones: Array of defined motion detection areas
    • Each zone has a UUID id and vertices array
    • Vertices define the polygon boundaries of the detection zone
  • component_id: On a multi-camera device, the camera module these zones belong to
Note: On a Ring Elite, the zones in a device-level configurations response belong to the single camera module named by motion_detection.component_id — not to the device as a whole. Don't apply one module's zones to every view. See Multi-Camera Devices.

Image Enhancements

  • color_night_vision: Color night vision setting ("on" or "off")
  • hdr: High Dynamic Range setting ("on" or "off")
  • ir_led_night_vision: Infrared LED night vision setting ("on" or "off")
  • auto_zoom_track: Automatic zoom and tracking setting ("on" or "off")
  • privacy_zones: Array of privacy zones that should be obscured (same structure as motion zones)

Per-component configurations

Scope the configurations request to one camera module with the component_id query parameter.

GET https://api.amazonvision.com/v1/devices/{device_id}/configurations?component_id=5
Authorization: Bearer <access_token>
{
  "data": {
    "type": "component-configurations",
    "id": "ava1.ring.component.configurations.XXXYYY",
    "attributes": {
      "component_name": "View 6",
      "component_id": "5",
      "component_type": "lens"
    }
  },
  "meta": {
    "time": "2026-08-14T13:42:16Z"
  }
}

The scoped response returns identity metadata only. Motion zones, video settings, and image enhancements aren't yet scoped per component — read them from the device-level configurations response, which describes the first component in components.items and names it in motion_detection.component_id.

Zone Coordinate System

Zones use a normalized coordinate system where all values are in the range 0.0 to 1.0:

  • Origin: Top-left corner (0, 0)
  • X-axis: 0.0 = left edge, 1.0 = right edge
  • Y-axis: 0.0 = top edge, 1.0 = bottom edge
  • Vertices: Define polygon boundaries as ordered points

To convert to pixel coordinates, multiply by the frame dimensions:

pixel_x = vertex['x'] * frame_width
pixel_y = vertex['y'] * frame_height
Warning: Mixed numeric types in JSON: Vertex x and y values are JSON numbers. When a value falls on a whole number (e.g., 0, 1), JSON parsers may deserialize it as an Integer rather than a Double. In Java, do not cast vertex values directly to (Double) — use ((Number) value).doubleValue() instead. In Python, this is not an issue.

Impact on Partner Operations

Video Encryption

def is_video_readable(device_config):
    encryption = device_config.get('attributes', {}).get('e2e_encryption', {})
    return not encryption.get('enabled', False)

Motion Detection Settings

def should_process_motion_events(device_config):
    motion_config = device_config.get('attributes', {}).get('motion_detection', {})
    return motion_config.get('enabled') == 'on'

def get_motion_zones(device_config):
    motion_config = device_config.get('attributes', {}).get('motion_detection', {})
    return motion_config.get('motion_zones', [])

Privacy Zone Handling

def get_privacy_zones(device_config):
    image_config = device_config.get('attributes', {}).get('image_enhancements', {})
    return image_config.get('privacy_zones', [])

def should_obscure_regions(device_config):
    privacy_zones = get_privacy_zones(device_config)
    return len(privacy_zones) > 0

Example Zone Processing

def point_in_polygon(point, vertices):
    """Check if a normalized point (0-1 range) is inside a polygon."""
    x, y = point
    n = len(vertices)
    inside = False
    
    p1x, p1y = vertices[0]['x'], vertices[0]['y']
    for i in range(1, n + 1):
        p2x, p2y = vertices[i % n]['x'], vertices[i % n]['y']
        if y > min(p1y, p2y):
            if y <= max(p1y, p2y):
                if x <= max(p1x, p2x):
                    if p1y != p2y:
                        xinters = (y - p1y) * (p2x - p1x) / (p2y - p1y) + p1x
                    if p1x == p2x or x <= xinters:
                        inside = not inside
        p1x, p1y = p2x, p2y
    
    return inside

Best Practices

  1. Respect privacy zones: Always obscure or avoid processing areas marked as privacy zones
  2. Check motion settings: Verify motion detection is enabled before expecting motion events
  3. Handle missing configs: Not all devices will have all configuration options
  4. Zone processing: Implement proper polygon intersection algorithms for zone handling
  5. Configuration changes: Be prepared for configurations to change over time
  6. Numeric type safety: Always handle vertex coordinates as Number (not Double or Integer) to avoid type cast errors

Multi-Camera Devices

Some Ring cameras contain more than one camera module in a single physical unit. Ring Elite cameras combine several camera modules covering different parts of a panoramic field of view, and report themselves to the API as one device.

Each camera module is a component, addressed by a component_id. Discovery tells you how many components a device has; a component_id query parameter lets you target one of them for capabilities, configurations, and live streaming.

The Ring Elite lineup

Model Camera modules
Ring Elite 140 Two
Ring Elite 180 Three
Ring Elite 270 Five
Ring Elite 360 Six
Important: The API doesn't tell you which model you're connected to. There's no model or SKU field in any response — attributes.name is a name the customer chose. Use this table to know what to expect across the lineup, but always read the actual camera count from components.items at runtime.

Detecting a multi-camera device

A multi-camera device exposes a components object in its device-capabilities resource. Single-camera devices omit components entirely — its absence is how you identify a standard single-camera device.

GET https://api.amazonvision.com/v1/devices?include=capabilities
Authorization: Bearer <access_token>

The components.items array lists every camera module in physical order. The following response is from a Ring Elite 360.

{
  "type": "device-capabilities",
  "id": "ava1.ring.device.capabilities.XXXYYY",
  "attributes": {
    "components": {
      "items": [
        { "component_id": "0", "component_type": "lens", "component_name": "View 1" },
        { "component_id": "1", "component_type": "lens", "component_name": "View 2" },
        { "component_id": "2", "component_type": "lens", "component_name": "View 3" },
        { "component_id": "3", "component_type": "lens", "component_name": "View 4" },
        { "component_id": "4", "component_type": "lens", "component_name": "View 5" },
        { "component_id": "5", "component_type": "lens", "component_name": "View 6" }
      ]
    }
  }
}

Component fields

Field Type Description
component_id string Identifier for one camera module. Pass this as the component_id query parameter.
component_type string Module type. "lens" for a camera module. Treat unrecognized values as unsupported and skip them.
component_name string Display label for the module, such as "View 1". Show this to users instead of the raw component_id.
Note: component_id is a string and opaque. Values on current devices look like "0" through "5", but don't parse them as integers, sort them numerically, or generate them yourself. Read them from components.items and pass them back unchanged.

Component order

components.items is ordered to match the physical arrangement of the camera modules on the device, and that order is stable across API calls. Use the array order — not the component_id value — when you lay out views side by side or build a panoramic composite.

Ring doesn't expose a pre-stitched panoramic stream through the partner API. To present a panorama, stream each component and compose the views in your own application using the array order.

Targeting a component

Two mechanisms, depending on the endpoint.

Query parameter — metadata and live streaming:

Endpoint Purpose
GET /v1/devices/{device_id}/capabilities?component_id=N Metadata for one camera module
GET /v1/devices/{device_id}/configurations?component_id=N Settings scoped to one camera module
POST /v1/devices/{device_id}/media/streaming/whep/sessions?component_id=N Live WHEP stream from one camera module
rtsps://video.rtsp.amazonvision.com:322/v1/devices/{device_id}/stream?component_id=N Live RTSP stream from one camera module

Request body — media downloads. These take a components array instead of a query parameter:

Endpoint Purpose
POST /v1/devices/{device_id}/media/video/download Recorded clip from one camera module
POST /v1/devices/{device_id}/media/image/download Snapshot from one camera module
"components": [
  { "component_id": "1" }
]

The array takes exactly one entry — one request per camera module. See Media Clips and Image Snapshots.

Default behavior when you omit component_id

For capabilities, configurations, and live streaming, omitting component_id targets the first component in components.items. Device-level capabilities and configurations responses describe that first component, and carry component_id inside the motion_detection and video blocks so you can confirm which one you're reading.

An application with no multi-camera support keeps working against a Ring Elite: it discovers one device, streams one view, and receives device-level webhook events. Single-camera devices ignore component_id entirely.

Warning: Media downloads are the exception — always send components in the request body. On a multi-camera device, the video and image download endpoints do not currently guarantee which camera module you get when components is omitted. Send it on every download request against a Ring Elite. See Media Clips and Image Snapshots.

Motion events

Motion webhooks fire at the device level. On a multi-camera device the payload carries a component_ids array naming the components that contributed to the event. The field is absent on single-camera devices.

{
  "data": {
    "type": "motion_detected",
    "attributes": {
      "source": "ava1.ring.device.XXXYYY",
      "source_type": "devices",
      "timestamp": 1786715596787,
      "sub_type": "motion",
      "component_ids": ["0"]
    }
  }
}

See Motion Detection for the full payload.

Working with a multi-camera device

The following code example discovers components and starts a stream for each one. It works unchanged across the Ring Elite lineup and on single-camera devices.

def get_components(device_capabilities):
    """Return component IDs for a device, or a single default entry."""
    components = device_capabilities.get("attributes", {}).get("components")
    if not components:
        return [None]              # Single-camera device — omit component_id
    return [c["component_id"] for c in components["items"]]


def stream_all_views(device_id, device_capabilities, access_token, sdp_offer):
    base = f"https://api.amazonvision.com/v1/devices/{device_id}/media/streaming/whep/sessions"
    sessions = []
    for component_id in get_components(device_capabilities):
        url = base if component_id is None else f"{base}?component_id={component_id}"
        sessions.append(start_whep_session(url, access_token, sdp_offer))
    return sessions

Because components takes a single entry, downloading every view means one request per module.

def download_all_views(device_id, device_capabilities, access_token, timestamp):
    """One request per camera module — components takes exactly one entry."""
    results = {}
    for component_id in get_components(device_capabilities):
        payload = {"type": "at_timestamp", "timestamp": timestamp}
        if component_id is not None:
            payload["components"] = [{"component_id": component_id}]
        results[component_id] = post_image_download(device_id, access_token, payload)
    return results
Important: Each component stream is a separate WHEP or RTSP session against the same device, and each counts toward the device's session duration limits. On a Ring Elite 360 that means six concurrent sessions if you open every view — open only the views the user is actually watching.

Related topics

  • Device Discovery — Discovering devices and sideloading capabilities
  • Capabilities — Per-component and device-level capability resources
  • Configurations — Per-component and device-level settings
  • WHEP Live Video Streaming — Per-component live streaming
  • Media Clips — Per-component recorded video downloads
  • Motion Detection — Motion webhook payload including component_ids

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-Signature header for message authenticity
  • JSON:API structure for consistent payload format
  • Event-driven architecture for real-time updates
  • Account ID in meta for 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
Important: Your webhook URL cannot be changed via API. Self-service webhook management is available via Development Portal.

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)
Warning: Always verify the HMAC signature before processing any webhook payload. Skipping verification exposes your application to spoofed webhook calls from malicious actors.
Warning: Pass the raw body bytes, not re-serialized JSON. raw_body must be the request body exactly as received (in Flask, request.data). If you parse the JSON and then re-serialize it to compute the HMAC, whitespace and key ordering change the bytes — the digest will not match and every signature check will fail with a payload that looks correct. Verify first, parse second.
Note: Encoding note — Webhooks vs. Nonces: Both webhook signatures and account-linking nonces use the same HMAC signing key and HMAC-SHA256 algorithm, but with different output encodings: Webhooks use hex string (.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.1 for 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
Note: Events from a multi-camera device carry an additional component_ids array inside attributes, naming the camera modules that contributed to the event. Events still fire at the device level. See Multi-Camera Devices.

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 attributes.sub_type (such as human), plus attributes.component_ids on multi-camera devices 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
App Integration Added app_integration_added A Ring user linked a partner integration App Integration Added
App Integration Removed app_integration_removed A Ring user unlinked a partner integration App Integration Removed

Webhook v1.1 payload structure — subscription lifecycle notifications

Subscription lifecycle webhooks follow the v1.1 payload format with account_id in the meta section:

{
  "meta": {
    "version": "1.1",
    "time": "2026-07-06T11:11:00Z",
    "request_id": "3c4484f3-4605-4bad-920d-4583cd2bef69",
    "account_id": "ava1.ring.account.XXXYYY"
  },
  "data": {
    "id": "<event_id>",
    "type": "subscription_activated",
    "attributes": {
      "source": "subscriptions",
      "source_id": "<subscription_id>",
      "sub_type": "paid",
      "plan_id": "<plan_id>",
      "expires_at": "2026-08-06T00:00:00Z"
    },
    "relationships": {
      "devices": {
        "data": [
          { "type": "device", "id": "<device_id>" }
        ]
      },
      "subscriptions": {
        "links": {
          "self": "/v1/accounts/me/subscriptions"
        }
      }
    }
  }
}

Meta fields

  • version: Always 1.1 for 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 and sub types

Ring supports the following subscription lifecycle event types:

Event Type Value Description Details
Subscription Activated subscription_activated A paid subscription or trial was activated Subscription Activated
Subscription Deactivated subscription_deactivated A paid subscription or trial was deactivated Subscription Deactivated

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.
Warning: If Ring receives persistent 4xx errors (e.g., 10+ consecutive failures over 24 hours), Ring may disable your webhook endpoint and notify your partner integration contact.

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.
Important: Your webhook handler must be idempotent — you may receive the same event more than once due to retries or network issues. Use the 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)
        elif event_type == 'app_integration_added':
            handle_app_integration_added(payload)
        elif event_type == 'app_integration_removed':
            handle_app_integration_removed(payload)
        elif event_type == 'subscription_activated':
            handle_subscription_activated(payload)
        elif event_type == 'subscription_deactivated':
            handle_subscription_deactivated(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
Signing re-serialized JSON Every signature check fails with 401, even though the payload looks correct Compute the HMAC over the raw body bytes as received (request.data), before parsing
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

  1. Verify HMAC signature: Always validate the X-Signature header
  2. Use account_id: Associate events with the correct Ring user via meta.account_id
  3. Validate payload: Check request structure and required fields
  4. Idempotency: Handle duplicate deliveries gracefully using request_id
  5. Rate limiting: Implement appropriate rate limiting on webhook endpoints
  6. Respond quickly: Return HTTP 200 within 5 seconds

Motion Detection

Motion detection notifications are sent when Ring devices detect movement in their field of view. These real-time notifications enable partners to respond immediately to motion events.

Webhook Delivery

Ring delivers all webhook notifications with an HMAC-SHA256 signature in the X-Signature header. Partners must verify this signature before processing the payload. See Notifications for signature verification details.

Webhook Payload

{
  "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>_motion_<timestamp>",
    "type": "motion_detected",
    "attributes": {
      "source": "<device_id>",
      "source_type": "devices",
      "timestamp": 1786715596787,
      "timestamp_readable": "2026-08-14 08:53:16",
      "sub_type": "motion",
      "component_ids": ["0"]
    },
    "relationships": {
      "devices": {
        "links": {
          "self": "/v1/devices/<device_id>"
        }
      }
    }
  }
}

Payload Fields

Meta Information

  • version: Webhook payload version (1.1 includes account_id)
  • 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 Data

  • id: Unique identifier for this specific motion event (format: <device_id>_<sub_type>_<timestamp>)
  • type: Always motion_detected for motion events
  • sub_type: Classification of the motion detected (for example, motion, human). Returned inside attributes.
  • source: Device ID that detected the motion
  • source_type: Always devices for device-originated events
  • timestamp: Epoch milliseconds when motion was detected
  • timestamp_readable: Human-readable rendering of timestamp, for logs and debugging. No timezone offset is included — use timestamp for any time calculation.
  • component_ids: Array of strings naming the camera modules that contributed to the event. Present only on multi-camera devices such as Ring Elite. See Multi-Camera Devices.

The values sub_type can carry depend on whether the device has Smart Alerts enabled. Smart Alerts is the Ring-side detection that classifies what triggered a motion event; customers turn it on per device in the Ring app.

Smart Alerts disabled. No classification is performed, and every motion event from the device carries one value:

Sub Type Description
motion General motion detected

Smart Alerts enabled. Ring classifies the trigger, and sub_type carries one of:

Sub Type Description
human Human detected
vehicle Vehicle detected
package_delivery Package delivery detected
other_motion Unable to categorize motion
Important: sub_type is returned inside attributes — read it as data.attributes.sub_type, not data.subType.
Note: No API exposes whether Smart Alerts is enabled on a device, so your app cannot check the setting before deciding how to read sub_type. Treat the field as an open-ended vocabulary: handle the values you act on, and pass anything else through rather than dropping it. See Refer to Ring app for device configuration settings.

Processing Motion Events

def handle_motion_detection(payload):
    attributes = payload['data']['attributes']
    device_id = attributes['source']
    account_id = payload['meta']['account_id']
    motion_timestamp = attributes['timestamp']
    event_id = payload['data']['id']
    sub_type = attributes.get('sub_type', 'unknown')

    # Multi-camera devices name the modules that triggered the event
    component_ids = attributes.get('component_ids', [])

    log_motion_event(device_id, account_id, motion_timestamp, event_id, sub_type, component_ids)
    trigger_motion_response(device_id, account_id, motion_timestamp, sub_type, component_ids)

    return {'status': 'processed'}

Best Practices

  1. Verify HMAC signature: Always verify the X-Signature header before processing
  2. Use account_id: Associate events with the correct Ring user via meta.account_id
  3. Handle sub_type: Use attributes.sub_type (e.g., human) to differentiate motion classifications
  4. Handle component_ids: On multi-camera devices, use attributes.component_ids to attribute the event to specific camera modules
  5. Handle duplicates: Implement idempotency using request_id or event id
  6. Respond quickly: Return HTTP 200 within 5 seconds to avoid timeout
  7. Respect configuration: Check device motion detection settings

Button Press

Button press notifications are sent when a Ring doorbell button is pressed. These real-time notifications enable partners to respond immediately to doorbell press events.

Webhook Delivery

Ring delivers all webhook notifications with an HMAC-SHA256 signature in the X-Signature header. Partners must verify this signature before processing the payload. See Notifications for signature verification details.

Webhook Payload

{
  "meta": {
    "version": "1.1",
    "time": "2026-02-14T00:02:53.027052438Z",
    "request_id": "c83081fc-2f1b-4cb7-8b18-b18a318b8bab",
    "account_id": "ava1.ring.account.XXXYYY"
  },
  "data": {
    "id": "<device_id>_button_press_<timestamp>",
    "type": "button_press",
    "attributes": {
      "source": "<device_id>",
      "source_type": "devices",
      "timestamp": 1771027372393
    },
    "relationships": {
      "devices": {
        "links": {
          "self": "/v1/devices/<device_id>"
        }
      }
    }
  }
}

Payload Fields

Meta Information

  • version: Webhook payload version (1.1 includes account_id)
  • 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 Data

  • id: Unique identifier (format: <device_id>_button_press_<timestamp>)
  • type: Always button_press for doorbell press events
  • source: Device ID of the doorbell that was pressed
  • source_type: Always devices
  • timestamp: Epoch milliseconds when the button was pressed

When Button Press Occurs

Button press notifications are triggered when:

  • A visitor presses the Ring doorbell button
  • The doorbell hardware button is physically activated

Checking for button press support

You can tell whether a device sends button press events before any event arrives. Its capabilities return button_press.supported: true on a doorbell, and no button_press object on other devices. Read it during discovery with GET /v1/devices?include=capabilities, and use it to decide which experience to offer for each device.

Processing Button Press Events

def handle_button_press(payload):
    device_id = payload['data']['attributes']['source']
    account_id = payload['meta']['account_id']
    press_timestamp = payload['data']['attributes']['timestamp']
    event_id = payload['data']['id']
    
    log_button_press(device_id, account_id, press_timestamp, event_id)
    trigger_doorbell_response(device_id, account_id, press_timestamp)
    
    return {'status': 'processed'}

Best Practices

  1. Verify HMAC signature: Always verify the X-Signature header before processing
  2. Use account_id: Associate events with the correct Ring user via meta.account_id
  3. Handle duplicates: Implement idempotency using request_id or event id
  4. Respond quickly: Return HTTP 200 within 5 seconds to avoid timeout
  5. Trigger video: Consider initiating a live video session after button press for visual verification

Device Addition

Device addition notifications are sent when a new Ring device becomes available for partner access. This occurs when a user links your app, adds devices to their account, or grants additional permissions to the partner integration.

Webhook Delivery

Ring delivers all webhook notifications with an HMAC-SHA256 signature in the X-Signature header. Partners must verify this signature before processing the payload. See Notifications for signature verification details.

Webhook Payload

{
  "meta": {
    "version": "1.1",
    "time": "2026-02-13T12:20:20.935994571Z",
    "request_id": "216c97be-4d57-45c1-a9f0-28c9f5442e8c",
    "account_id": "ava1.ring.account.XXXYYY"
  },
  "data": {
    "id": "<device_id>_device_added_<timestamp>",
    "type": "device_added",
    "attributes": {
      "source": "<device_id>",
      "source_type": "devices",
      "timestamp": 1770985219558
    },
    "relationships": {
      "devices": {
        "links": {
          "self": "/v1/devices/<device_id>"
        }
      }
    }
  }
}

When Device Addition Occurs

Device addition notifications are triggered when:

  • Ring verifies the nonce in your POST to /v1/accounts/me/app-integrations during one-way account linking: one event for each device the user shared
  • User installs a new Ring device and grants partner access
  • User modifies integration settings to include additional devices
  • Device permissions are restored after being temporarily revoked
Note: Initial device list: The device_added events from linking arrive within seconds of your POST, while the integration is still awaiting, and can arrive before the POST call returns. Process them normally, and still make sure your PATCH to completed succeeds. Delivery is at-least-once, so after the PATCH succeeds, call GET /v1/devices for the initial device list, and use device_added and device_removed events to track changes from then on.

Processing Device Addition

def handle_device_addition(payload):
    device_id = payload['data']['attributes']['source']
    account_id = payload['meta']['account_id']
    
    # Fetch full device information
    device_info = fetch_device_details(device_id)
    
    if device_info:
        store_new_device(device_info, account_id)
        setup_device_monitoring(device_id)
        return {'status': 'device_added'}
    else:
        return {'status': 'error', 'message': 'Failed to fetch device details'}

Best Practices

  1. Verify HMAC signature: Always verify the X-Signature header before processing
  2. Use account_id: Associate events with the correct Ring user via meta.account_id
  3. Immediate processing: Process device additions promptly to enable functionality
  4. Fetch device info: Call GET /v1/devices/{id}?include=capabilities,status after receiving the notification
  5. Handle duplicates: Implement idempotency using request_id or event id
  6. Respond quickly: Return HTTP 200 within 5 seconds to avoid timeout

Device Removal

Device removal notifications are sent when a partner loses access to a Ring device. This occurs when users revoke permissions, remove devices, or modify their integration settings.

Webhook Delivery

Ring delivers all webhook notifications with an HMAC-SHA256 signature in the X-Signature header. Partners must verify this signature before processing the payload. See Notifications for signature verification details.

Webhook Payload

{
  "meta": {
    "version": "1.1",
    "time": "2026-02-13T19:28:04.020279817Z",
    "request_id": "668b822c-266b-43c4-a1f0-6045bc5f3b1c",
    "account_id": "ava1.ring.account.XXXYYY"
  },
  "data": {
    "id": "<device_id>_device_removed_<timestamp>",
    "type": "device_removed",
    "attributes": {
      "source": "<device_id>",
      "source_type": "devices",
      "timestamp": 1771010883164
    },
    "relationships": {
      "devices": {
        "links": {
          "self": "/v1/devices/<device_id>"
        }
      }
    }
  }
}

When Device Removal Occurs

Device removal notifications are triggered when:

  • User explicitly removes device from partner integration
  • User deletes the device from their Ring account
  • User revokes partner access permissions
  • Device is permanently offline or decommissioned
  • Integration is disconnected or disabled

Processing Device Removal

def handle_device_removal(payload):
    device_id = payload['data']['attributes']['source']
    account_id = payload['meta']['account_id']
    removal_timestamp = payload['data']['attributes']['timestamp']
    
    # Stop active streaming sessions
    terminate_active_streams(device_id)
    
    # Cancel scheduled operations
    cancel_scheduled_operations(device_id)
    
    # Clean up stored data
    cleanup_device_resources(device_id)
    
    # Update device status in partner system
    mark_device_removed(device_id, account_id, removal_timestamp)
    
    return {'status': 'device_removed'}

Post-Removal Behavior

After device removal:

  • All API requests for the device will return 404 errors
  • Active streaming sessions are terminated
  • Scheduled operations are cancelled
  • Historical data may be preserved for audit purposes

Best Practices

  1. Verify HMAC signature: Always verify the X-Signature header before processing
  2. Use account_id: Associate events with the correct Ring user via meta.account_id
  3. Immediate cleanup: Process removal notifications promptly to free resources
  4. Graceful termination: Close active sessions and operations cleanly
  5. Data preservation: Archive important data before cleanup
  6. User communication: Inform users about device removal
  7. Audit logging: Log all removal events for compliance
  8. Idempotency: Handle duplicate removal notifications safely

Device Online

Device online notifications are sent when a Ring device comes online and becomes available for interaction. These real-time notifications enable partners to know when devices are reachable for streaming, configuration, or other operations.

Webhook Delivery

Ring delivers all webhook notifications with an HMAC-SHA256 signature in the X-Signature header. Partners must verify this signature before processing the payload. See Notifications for signature verification details.

Webhook Payload

{
  "meta": {
    "version": "1.1",
    "time": "2026-02-13T23:32:51.234231900Z",
    "request_id": "ab058f6f-8edd-4e92-9796-956ddc4d739d",
    "account_id": "ava1.ring.account.XXXYYY"
  },
  "data": {
    "id": "<device_id>_device_online_<timestamp>",
    "type": "device_online",
    "attributes": {
      "source": "<device_id>",
      "source_type": "devices",
      "timestamp": 1771025567000
    },
    "relationships": {
      "devices": {
        "links": {
          "self": "/v1/devices/<device_id>"
        }
      }
    }
  }
}

When Device Online Occurs

Device online notifications are triggered when:

  • A device reconnects to the network after being offline
  • A device powers on after being powered off
  • A device restores connectivity after a network interruption

Processing Device Online Events

def handle_device_online(payload):
    device_id = payload['data']['attributes']['source']
    account_id = payload['meta']['account_id']
    online_timestamp = payload['data']['attributes']['timestamp']
    
    update_device_status(device_id, account_id, 'online', online_timestamp)
    resume_device_operations(device_id)
    
    return {'status': 'processed'}

Best Practices

  1. Verify HMAC signature: Always verify the X-Signature header before processing
  2. Use account_id: Associate events with the correct Ring user via meta.account_id
  3. Update status tracking: Maintain device online/offline status in your system
  4. Resume operations: Re-enable any features that were paused while the device was offline
  5. Handle duplicates: Implement idempotency using request_id or event id
  6. Respond quickly: Return HTTP 200 within 5 seconds to avoid timeout

Device Offline

Device offline notifications are sent when a Ring device goes offline and is no longer reachable. These real-time notifications enable partners to handle device unavailability gracefully and inform users.

Webhook Delivery

Ring delivers all webhook notifications with an HMAC-SHA256 signature in the X-Signature header. Partners must verify this signature before processing the payload. See Notifications for signature verification details.

Webhook Payload

{
  "meta": {
    "version": "1.1",
    "time": "2026-02-13T23:26:49.710448203Z",
    "request_id": "343385ae-1de8-4b29-85aa-07bd4b7112fd",
    "account_id": "ava1.ring.account.XXXYYY"
  },
  "data": {
    "id": "<device_id>_device_offline_<timestamp>",
    "type": "device_offline",
    "attributes": {
      "source": "<device_id>",
      "source_type": "devices",
      "timestamp": 1771025206000
    },
    "relationships": {
      "devices": {
        "links": {
          "self": "/v1/devices/<device_id>"
        }
      }
    }
  }
}

When Device Offline Occurs

Device offline notifications are triggered when:

  • A device loses network connectivity
  • A device powers off or runs out of battery
  • A device experiences a hardware or firmware issue preventing communication

Processing Device Offline Events

def handle_device_offline(payload):
    device_id = payload['data']['attributes']['source']
    account_id = payload['meta']['account_id']
    offline_timestamp = payload['data']['attributes']['timestamp']
    
    update_device_status(device_id, account_id, 'offline', offline_timestamp)
    terminate_active_streams(device_id)
    pause_device_operations(device_id)
    
    return {'status': 'processed'}

Best Practices

  1. Verify HMAC signature: Always verify the X-Signature header before processing
  2. Use account_id: Associate events with the correct Ring user via meta.account_id
  3. Update status tracking: Mark the device as offline in your system
  4. Terminate active sessions: Close any active video streams for the device
  5. Pause operations: Suspend scheduled operations until the device comes back online
  6. Handle duplicates: Implement idempotency using request_id or event id
  7. Respond quickly: Return HTTP 200 within 5 seconds to avoid timeout

App Integration Added

App integration added notifications are sent when a Ring user links your partner integration to their Ring account. In one-way account linking, Ring sends this event when it verifies the nonce in your POST to /v1/accounts/me/app-integrations. It arrives while the integration is still awaiting, and can arrive before the POST call returns. The link isn't complete until your PATCH with status: completed succeeds.

Webhook delivery

Ring delivers all webhook notifications with an HMAC-SHA256 signature in the X-Signature header. Partners must verify this signature before processing the payload. See Notifications for signature verification details.

Webhook payload

{
  "meta": {
    "version": "1.1",
    "time": "2026-07-06T11:11:00Z",
    "request_id": "3c4484f3-4605-4bad-920d-4583cd2bef69",
    "account_id": "ava1.ring.account.XXXYYY"
  },
  "data": {
    "id": "<event_id>",
    "type": "app_integration_added",
    "attributes": {
      "source": "<account_id>",
      "source_type": "users",
      "timestamp": 1699457230000
    }
  }
}

Note: The relationships block is not included in this event. After integration addition, the partner's access to the user's devices is established through the account linking flow.

Payload fields

Meta information

  • version: Webhook payload version (1.1 includes account_id)
  • 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 who added the integration

Event data

  • id: Unique event identifier (opaque — do not parse)
  • type: Always app_integration_added for this event
  • source: The Account ID of the Ring user who triggered the event
  • source_type: Always "users" — integration events are user-initiated actions
  • timestamp: Epoch milliseconds when the integration was added

When app integration addition occurs

This event is sent when:

  • Ring verifies the nonce in your POST to /v1/accounts/me/app-integrations during one-way account linking

Processing app integration added

def handle_app_integration_added(payload):
    account_id = payload['meta']['account_id']
    request_id = payload['meta']['request_id']
    event_timestamp = payload['data']['attributes']['timestamp']

    # Deduplicate using request_id
    if is_duplicate_event(request_id):
        return {'status': 'duplicate'}
    mark_event_processed(request_id)

    # Initialize the user's integration in your system
    initialize_integration(account_id, event_timestamp)

    return {'status': 'processed'}

Best practices

  1. Verify HMAC signature: Always verify the X-Signature header before processing
  2. Use account_id: Associate events with the correct Ring user via meta.account_id
  3. Handle duplicates: Implement idempotency using request_id
  4. Respond quickly: Return HTTP 200 within 5 seconds to avoid timeout

App Integration Removed

App integration removed notifications are sent when a Ring user unlinks or removes your partner integration from their Ring account. This event signals that the partner's access to the user's account, devices, and data has been revoked.

Webhook delivery

Ring delivers all webhook notifications with an HMAC-SHA256 signature in the X-Signature header. Partners must verify this signature before processing the payload. See Notifications for signature verification details.

Webhook payload

{
  "meta": {
    "version": "1.1",
    "time": "2026-07-06T11:11:00Z",
    "request_id": "3c4484f3-4605-4bad-920d-4583cd2bef69",
    "account_id": "ava1.ring.account.XXXYYY"
  },
  "data": {
    "id": "<event_id>",
    "type": "app_integration_removed",
    "attributes": {
      "source": "<account_id>",
      "source_type": "users",
      "timestamp": 1699457230000
    }
  }
}

Note: The relationships block is not included in this event. After integration removal, the partner's AVA access and refresh tokens are revoked — any API calls using those tokens fail with 401 Unauthorized.

Payload fields

Meta information

  • version: Webhook payload version (1.1 includes account_id)
  • 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 who removed the integration

Event data

  • id: Unique event identifier (opaque — do not parse)
  • type: Always app_integration_removed for this event
  • source: The Account ID of the Ring user who triggered the removal
  • source_type: Always "users" — integration events are user-initiated actions
  • timestamp: Epoch milliseconds when the integration was removed

When app integration removal occurs

This event is sent when:

  • A Ring user removes or unlinks your partner integration from their Ring account

Processing app integration removed

def handle_app_integration_removed(payload):
    account_id = payload['meta']['account_id']
    request_id = payload['meta']['request_id']
    event_timestamp = payload['data']['attributes']['timestamp']

    # Deduplicate using request_id
    if is_duplicate_event(request_id):
        return {'status': 'duplicate'}
    mark_event_processed(request_id)

    # Clean up the user's integration
    terminate_active_sessions(account_id)
    cancel_scheduled_operations(account_id)
    remove_cached_tokens(account_id)
    mark_integration_removed(account_id, event_timestamp)

    return {'status': 'processed'}

Best practices

  1. Verify HMAC signature: Always verify the X-Signature header before processing
  2. Use account_id: Associate events with the correct Ring user via meta.account_id
  3. Handle duplicates: Implement idempotency using request_id
  4. Respond quickly: Return HTTP 200 within 5 seconds to avoid timeout
  5. Clean up immediately: End sessions, remove cached tokens, and cancel pending operations
  6. Do not call Ring APIs: Your tokens are revoked after removal — any API calls return 401

Subscription Activated

Subscription activated notifications are sent when a user's paid subscription or trial is activated for your app. This event signals that you should grant access to your services for the associated devices.

Webhook Delivery

Ring delivers all webhook notifications with an HMAC-SHA256 signature in the X-Signature header. Partners must verify this signature before processing the payload. See Notifications for signature verification details.

Webhook Payload

{
  "meta": {
    "version": "1.1",
    "time": "2026-07-06T11:11:00Z",
    "request_id": "3c4484f3-4605-4bad-920d-4583cd2bef69",
    "account_id": "ava1.ring.account.XXXYYY"
  },
  "data": {
    "id": "<event_id>",
    "type": "subscription_activated",
    "attributes": {
      "source": "subscriptions",
      "source_id": "<subscription_id>",
      "sub_type": "paid",
      "plan_id": "<plan_id>",
      "expires_at": "2026-08-06T00:00:00Z"
    },
    "relationships": {
      "devices": {
        "data": [
          { "type": "device", "id": "<device_id>" }
        ]
      },
      "subscriptions": {
        "links": {
          "self": "/v1/accounts/me/subscriptions"
        }
      }
    }
  }
}

Payload fields

Meta information

  • version: Webhook payload version (1.1 includes account_id)
  • 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 data

  • id: Unique event identifier (opaque — do not parse)
  • type: Always subscription_activated for this event
  • source: Always "subscriptions" for subscription events
  • source_id: Directed subscription ID — the same ID returned by the Subscriptions API
  • sub_type: Type of subscription activated:
    • "paid" — Paid subscription (purchase, renewal, or reactivation)
    • "trial" — Free trial enrollment or trial resumed
  • plan_id: Subscription plan UUID. Present for paid subscriptions. null for trials.
  • expires_at: ISO 8601 date when the billing cycle or trial period ends. Always present for subscription_activated.

Relationships

  • devices: Array of directed device IDs covered by this subscription. Device type is "device" (singular).
  • subscriptions.links.self: Link to the Subscriptions API endpoint to query full subscription state

When subscription activation occurs

This event is sent when:

  • A user purchases a new paid subscription for your app
  • A paid subscription renews successfully
  • A paid subscription is reactivated
  • A user enrolls in a free trial for your app
  • A suspended trial is resumed

Recommended: fetch full state via Subscriptions API

The webhook payload notifies you that a subscription state change occurred. For the most reliable integration, use the webhook as a trigger and then call the Subscriptions API to retrieve the full, current subscription state:

import requests

def handle_subscription_activated(payload, ava_token):
    account_id = payload['meta']['account_id']
    request_id = payload['meta']['request_id']

    # Deduplicate using request_id
    if is_duplicate_event(request_id):
        return {'status': 'duplicate'}
    mark_event_processed(request_id)

    # Fetch full subscription state from the Subscriptions API
    response = requests.get(
        "https://api.amazonvision.com/v1/accounts/me/subscriptions",
        headers={"Authorization": f"Bearer {ava_token}"}
    )

    if response.status_code == 200:
        subscriptions = response.json().get('data', [])
        for sub in subscriptions:
            sub_type = sub['attributes']['sub_type']
            status = sub['attributes']['status']
            plan_id = sub['attributes'].get('plan_id')
            expires_at = sub['attributes']['expires_at']
            devices = [d['id'] for d in sub['relationships']['devices']['data']]

            if status == 'active':
                grant_access(account_id, devices, sub_type, plan_id, expires_at)

    return {'status': 'processed'}

This approach ensures you always have the latest subscription state, even if webhooks arrive out of order or are duplicated.

Best Practices

  1. Verify HMAC signature: Always verify the X-Signature header before processing
  2. Use account_id: Associate events with the correct Ring user via meta.account_id
  3. Grant access immediately: Provision services for the user's devices as soon as this event is received
  4. Fetch full state: Call the Subscriptions API after receiving the webhook to get the complete, current subscription state
  5. Handle both sub_types: Your integration should handle both "paid" and "trial" activations
  6. Handle duplicates: Implement idempotency using request_id
  7. Respond quickly: Return HTTP 200 within 5 seconds to avoid timeout
  8. Use expires_at: Track the expiration date to handle subscription end gracefully

Subscription Deactivated

Subscription deactivated notifications are sent when a user's paid subscription or trial is deactivated for your app. This event signals that you should revoke access immediately for the associated devices.

Webhook Delivery

Ring delivers all webhook notifications with an HMAC-SHA256 signature in the X-Signature header. Partners must verify this signature before processing the payload. See Notifications for signature verification details.

Webhook Payload

{
  "meta": {
    "version": "1.1",
    "time": "2026-07-06T11:11:00Z",
    "request_id": "3c4484f3-4605-4bad-920d-4583cd2bef69",
    "account_id": "ava1.ring.account.XXXYYY"
  },
  "data": {
    "id": "<event_id>",
    "type": "subscription_deactivated",
    "attributes": {
      "source": "subscriptions",
      "source_id": "<subscription_id>",
      "sub_type": "paid",
      "plan_id": "<plan_id>"
    },
    "relationships": {
      "devices": {
        "data": [
          { "type": "device", "id": "<device_id>" }
        ]
      },
      "subscriptions": {
        "links": {
          "self": "/v1/accounts/me/subscriptions"
        }
      }
    }
  }
}
Note: The expires_at field is not present in deactivation events. Revoke access immediately upon receiving this event.

Payload fields

Meta information

  • version: Webhook payload version (1.1 includes account_id)
  • 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 data

  • id: Unique event identifier (opaque — do not parse)
  • type: Always subscription_deactivated for this event
  • source: Always "subscriptions" for subscription events
  • source_id: Directed subscription ID
  • sub_type: Type of subscription deactivated:
    • "paid" — Paid subscription deactivated
    • "trial" — Free trial cancelled or suspended
  • plan_id: Subscription plan UUID. Present for paid subscriptions. null for trials.

Relationships

  • devices: Array of directed device IDs that were covered by this subscription. Device type is "device" (singular).
  • subscriptions.links.self: Link to the Subscriptions API endpoint to query full subscription state

Differences from subscription_activated

Field subscription_activated subscription_deactivated
data.type "subscription_activated" "subscription_deactivated"
attributes.expires_at Present (ISO 8601) Not present

When subscription deactivation occurs

This event is sent when:

  • A user's Ring subscription is cancelled (cascading cancellation of all partner subscriptions)
  • A paid subscription payment fails
  • A user cancels their paid subscription (at end of billing cycle)
  • A user cancels their free trial
  • A trial is suspended (user loses Ring subscription on the device)

Recommended: fetch full state via Subscriptions API

Use the webhook as a trigger and call the Subscriptions API to confirm the current subscription state before revoking access:

import requests

def handle_subscription_deactivated(payload, ava_token):
    account_id = payload['meta']['account_id']
    request_id = payload['meta']['request_id']

    # Deduplicate using request_id
    if is_duplicate_event(request_id):
        return {'status': 'duplicate'}
    mark_event_processed(request_id)

    # Fetch current subscription state from the Subscriptions API
    response = requests.get(
        "https://api.amazonvision.com/v1/accounts/me/subscriptions",
        headers={"Authorization": f"Bearer {ava_token}"}
    )

    if response.status_code == 200:
        subscriptions = response.json().get('data', [])
        active_device_ids = set()
        for sub in subscriptions:
            if sub['attributes']['status'] == 'active':
                for d in sub['relationships']['devices']['data']:
                    active_device_ids.add(d['id'])

        # Revoke access for devices no longer covered by any active subscription
        deactivated_devices = [
            d['id'] for d in payload['data']['relationships']['devices']['data']
        ]
        for device_id in deactivated_devices:
            if device_id not in active_device_ids:
                revoke_access(account_id, device_id)

    return {'status': 'processed'}

This approach prevents accidentally revoking access if the user has another active subscription covering the same device.

Best Practices

  1. Verify HMAC signature: Always verify the X-Signature header before processing
  2. Use account_id: Associate events with the correct Ring user via meta.account_id
  3. Revoke access immediately: Do not wait for an expiration — deactivation means access ends now
  4. Fetch full state: Call the Subscriptions API to confirm which devices still have active coverage before revoking
  5. Handle both sub_types: Your integration should handle both "paid" and "trial" deactivations
  6. Handle duplicates: Implement idempotency using request_id
  7. Respond quickly: Return HTTP 200 within 5 seconds to avoid timeout
  8. Clean up resources: End active sessions, cancel scheduled operations, and remove cached data for revoked devices

Sensors

Early Access: Sensor support is in Early Access for developers. The endpoints, status attributes, and event types described in this section can change before general availability. You can integrate sensor API calls into your app, but your app can only be published after dynamic scopes reach general availability for Ring customers.

Ring sensors report physical conditions: a door opening, water reaching a floor, a temperature crossing a threshold. A sensor is a devices resource, so the device endpoints you already use for cameras and doorbells work unchanged. What differs is that a sensor has no camera and no media endpoints.

This section covers all Ring sensors. Behavior is shared across devices unless a device subsection says otherwise.

Quick reference

The following table shows the sensors this section covers.

Device Reports Events
Contact sensors, indoor and outdoor Door and window open and close, tamper contact_sensor_faulted, contact_sensor_cleared, tamper_detected, tamper_cleared
Flood/freeze sensor Water, freezing, tamper flood_detected, flood_cleared, freeze_detected, freeze_cleared, tamper_detected, tamper_cleared
Indoor temperature/humidity sensor Temperature and humidity thresholds temperature_exceeded, temperature_cleared, humidity_exceeded, humidity_cleared
Indoor air quality monitor PM2.5, carbon monoxide, temperature, and humidity thresholds pm25_exceeded, pm25_cleared, co_exceeded, co_cleared, temperature_exceeded, temperature_cleared, humidity_exceeded, humidity_cleared

Every sensor also emits device_online and device_offline. See Connectivity events for sensors.

Ring cameras, doorbells, and chimes are not sensors for the purposes of this documentation. Their events are covered by Motion Detection, Button Press, and the other camera event sections.

Sensor device discovery

Sensors are returned by the same call as your cameras and doorbells.

GET /v1/devices HTTP/1.1
Authorization: Bearer <access_token>

There is no sensor-specific discovery endpoint and no sensor-specific query endpoint.

Endpoints that apply to sensors

Endpoint Notes
GET /v1/devices Sensors appear in the same collection as cameras
GET /v1/devices/{device_id}  
GET /v1/devices/{device_id}/status Attribute set varies by device. On devices that measure a value, includes the last reported reading.
GET /v1/devices/{device_id}/capabilities Declares whether the device supports battery reporting. Read it before trusting battery_status.
GET /v1/devices/{device_id}/location  
GET /v1/devices/{device_id}/configurations Returns the configured temperature and humidity thresholds on devices that have them. Remaining attributes are camera-oriented and null.
?include=status,capabilities,location Mechanics unchanged
Important: Filter your device collection before calling a media endpoint. No sensor has a camera, so the live video, WHEP, RTSP, media clip, and image snapshot endpoints do not apply to any of them. GET /v1/devices returns sensors alongside your cameras, so code that iterates every device and calls a streaming or download endpoint will hit sensors.

Identifying a sensor

Guidance Detail
Do not infer device type from capabilities Capability keys are shared across device families. Alarm motion sensors declare motion_detection, the same key a doorbell declares, so the presence of that key does not identify a camera device.
Store your own device type mapping at setup time Sensors that share a signal share their attributes and event types. A temperature threshold event from an air quality monitor is indistinguishable from one sent by a temperature/humidity sensor.

Common sensor telemetry

Every sensor returns these attributes from GET /v1/devices/{device_id}/status.

"attributes": {
  "online": true,
  "reported_at": "2026-03-10T14:20:05.118Z",
  "signal_strength": {
    "value": "good"
  },
  "battery_status": {
    "percentage": 85
  },
  "audio": {
    "snooze": {
      "active": null,
      "until": null
    }
  },
  "state": null
}
Attribute Description
online Whether Ring currently considers the device reachable.
reported_at Last check-in time, as an ISO 8601 timestamp. Updated on every device uplink.
signal_strength.value Signal quality.
battery_status.percentage Integer percentage on a battery-powered device. Values pass through exactly as the device reports them.
sensor_reporting_state.value Reads active even on a device that has never reported, so it does not indicate that the sensor is currently reporting. Read online and reported_at for liveness.
Important: Gate on online before reading any other attribute. A device that has not yet checked in returns placeholder values rather than an error, and the placeholder is not consistent: battery_status and signal_strength are absent entirely, while reported_at, online, and state return either null or, for reported_at, the Unix epoch 1970-01-01T00:00:00Z. Code that reads these keys directly raises on a newly added device, and a comparison against the epoch silently treats the device as having checked in decades ago.

Detection attributes by device

Attribute Contact Flood/Freeze Temp/Humidity Air Quality
contact_detection.faulted yes — — —
flood_detection.faulted — yes — —
freeze_detection.faulted — yes — —
tamper_detection.detected yes yes — —
sensor_reporting_state.value yes yes — —
Important: Read capabilities before trusting battery_status.percentage. GET /v1/devices/{device_id}/capabilities returns battery_status.supported on a battery-powered device and battery_status: null on one that is not. A device that does not support battery reporting still returns a battery_status.percentage in status, but the value is outside the 0–100 range — the air quality monitor reports 255. Treat an out-of-range value as no battery rather than as a level.
Important: Detection attributes are booleans, and faulted: true means the condition is active. An open door reports contact_detection.faulted: true. The faulted naming is inherited from the underlying protocol and does not read intuitively. Tamper alone uses detected rather than faulted.

The same contact sensor closed, then open.

"contact_detection": {
  "faulted": false
}
"contact_detection": {
  "faulted": true
}

Reading attributes by device

A device that measures a value reports it in the status response, alongside the common telemetry.

Device Reading attributes
Indoor temperature/humidity sensor temperature, humidity
Indoor air quality monitor temperature, humidity, pm2_5, co

Contact sensors and the flood/freeze sensor report detection attributes rather than readings.

Temperature is in degrees Celsius, humidity is a relative percentage, and PM2.5 is in µg/m³. All are floats.

"temperature": 24.2,
"humidity": 44.3,
"pm2_5": 3.5,
"co": 0.0
Important: Status returns the last reported reading, not a live one. The value is whatever the device sent at its most recent check-in, so read reported_at alongside it to judge how current it is. Observed readings have lagged the response by as much as an hour.
Warning: PM2.5 has a different field name in status and in events. The status response calls it pm2_5; the webhook payload calls it pm25_level. Temperature and humidity use the same name on both surfaces. Code that shares a parser across the two will miss the particulate reading on one of them.

Sensor configuration

GET /v1/devices/{device_id}/configurations returns the temperature and humidity thresholds that drive threshold events, on devices that have them.

"temperature_threshold": {
  "high": 32.2,
  "low": 25.0
},
"humidity_threshold": {
  "high": 85.0,
  "low": 65.0
}
Attribute Description
temperature_threshold.high, temperature_threshold.low Upper and lower temperature bounds, in degrees Celsius.
humidity_threshold.high, humidity_threshold.low Upper and lower relative humidity bounds, as percentages.

Crossing either bound emits the matching threshold event. A reading below low or above high produces an _exceeded event, and a return between them produces a _cleared event.

The response also carries motion_detection, image_enhancements, and audio. These are camera-oriented and null on every sensor.

Note: Thresholds are read-only, and no endpoint for changing them is available. Only temperature and humidity bounds appear here. The air quality monitor's PM2.5 and carbon monoxide limits exist but are not readable.

Sensor events

Sensor state changes are delivered as webhooks. Ring signs every notification with an HMAC-SHA256 signature in the X-Signature header. Verify the signature before processing the payload, and return a 2xx response within 5 seconds. See Notifications.

Webhook delivery is the only source of sensor event data, which makes one field unusually important. Deduplicate on meta.request_id, the unique identifier Ring assigns to each delivery, because no event history exists to reconcile a duplicate against. Treat the value as opaque.

Event reference

Each table below uses the same four columns. Every event uses the sensor webhook payload with data.type set to the type value shown.

Contact sensors.

Event Type value Trigger Payload attributes
Contact Sensor Faulted contact_sensor_faulted The contact opens —
Contact Sensor Cleared contact_sensor_cleared The contact closes —
Tamper Detected tamper_detected The device is removed from its mount or physically altered —
Tamper Cleared tamper_cleared The tamper condition ends —

Flood/freeze sensor.

Event Type value Trigger Payload attributes
Flood Detected flood_detected Water reaches the level the sensor detects —
Flood Cleared flood_cleared Water is no longer detected —
Freeze Detected freeze_detected Internal temperature falls below 40 °F (4 °C) —
Freeze Cleared freeze_cleared Internal temperature rises above 40 °F (4 °C) —
Tamper Detected tamper_detected The device is physically altered —
Tamper Cleared tamper_cleared The tamper condition ends —

Indoor temperature/humidity sensor.

Event Type value Trigger Payload attributes
Temperature Exceeded temperature_exceeded Temperature moves outside its threshold temperature, threshold_type
Temperature Cleared temperature_cleared Temperature returns within threshold temperature
Humidity Exceeded humidity_exceeded Humidity moves outside its threshold humidity, threshold_type
Humidity Cleared humidity_cleared Humidity returns within threshold humidity

Indoor air quality monitor.

Event Type value Trigger Payload attributes
PM2.5 Exceeded pm25_exceeded Particulate level rises above its threshold pm25_level
PM2.5 Cleared pm25_cleared Particulate level returns to normal pm25_level
CO Exceeded co_exceeded Carbon monoxide rises above its threshold —
CO Cleared co_cleared Carbon monoxide returns to normal —
Temperature Exceeded temperature_exceeded Temperature moves outside its threshold temperature, threshold_type
Temperature Cleared temperature_cleared Temperature returns within threshold temperature
Humidity Exceeded humidity_exceeded Humidity moves outside its threshold humidity, threshold_type
Humidity Cleared humidity_cleared Humidity returns within threshold humidity

Connectivity events for sensors

device_online and device_offline apply to every sensor and use the payload documented on Device Online and Device Offline.

device_online is emitted on any device uplink, so for sensors it functions as a heartbeat rather than as a connectivity transition. Sensor device_offline results from a heartbeat timeout rather than from a directly observable transition, so expect it to lag the actual loss of connectivity.

Sensor webhook payload

Every sensor event shares this envelope, varying only in data.type and, for threshold events, the added reading.

{
  "meta": {
    "version": "1.1",
    "time": "2026-03-10T14:22:20.258000314Z",
    "request_id": "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx-1773152538431-contact_senso",
    "account_id": "ava1.ring.account.XXXXXXXX"
  },
  "data": {
    "id": "<device_id>_<event_type>_<timestamp>",
    "type": "contact_sensor_faulted",
    "attributes": {
      "source": "<device_id>",
      "source_type": "devices",
      "timestamp": 1773152538431
    },
    "relationships": {
      "devices": {
        "links": {
          "self": "/v1/devices/<device_id>"
        }
      }
    }
  }
}

Identifiers in this and every other example are placeholders.

Field Description
data.id Composed of the device ID, the event type, and the event timestamp.
data.type The event type. Route on this field.
data.attributes.source Device ID of the sensor that emitted the event. Read the device from this field.
data.attributes.source_type Always devices.
data.attributes.timestamp Unix epoch milliseconds when the state change occurred. Use this as the event time.
Warning: Route on data.type and read the device from data.attributes.source. For detection events the event type is the entire signal, because no attribute carries the new state. Do not parse data.id to recover the device ID: its internal structure is not a documented contract and may change without notice.

Threshold event attributes

The temperature/humidity sensor and the air quality monitor extend the envelope with the measured reading.

Attribute Appears on Description
temperature temperature_exceeded, temperature_cleared Measured temperature in degrees Celsius, as a float.
humidity humidity_exceeded, humidity_cleared Measured relative humidity as a percentage, as a float.
pm25_level pm25_exceeded, pm25_cleared Measured PM2.5 concentration in µg/m³, as a float.
threshold_type temperature_exceeded, humidity_exceeded Which bound was crossed, either min or max. Present only on measurements that have both an upper and a lower bound.

A humidity breach on the temperature/humidity sensor, carrying both the reading and the direction.

"attributes": {
  "source": "<device_id>",
  "source_type": "devices",
  "timestamp": 1787767836967,
  "humidity": 61.7,
  "threshold_type": "max"
}

The matching cleared event, carrying the reading alone.

"attributes": {
  "source": "<device_id>",
  "source_type": "devices",
  "timestamp": 1787767772030,
  "humidity": 55.6
}
Important: The payload names the bound that was crossed, not its value. threshold_type tells you whether a minimum or a maximum was passed, and the reading tells you by how much only after you look up the bound. Read the configured thresholds from Sensor configuration to recover the limit, and to work out which bound a temperature_cleared or humidity_cleared event returned inside, because cleared events carry no threshold_type.

Event delivery and retention

Warning: A sensor event that you do not store cannot be recovered. Event History supports camera and doorbell events only, so no sensor event is queryable and there is no backfill path if you miss one. Persist every event on receipt, before any downstream processing, and treat the status endpoint as your authority for current state: status reflects every state change, including changes whose webhooks were dropped.
Guidance Detail
Deduplicate on meta.request_id Required rather than advisory, because you cannot reconcile duplicates against a canonical event list.
Do not infer "no change" from silence An absent cleared event does not mean the condition persists.
Reconcile against status after rapid event sequences Poll the detection attribute to confirm current state.
Request dead letter queue replays within 72 hours For delivery failures. See Notifications.

Contact sensors

Ring contact sensors report when a door or window opens and closes. The indoor and outdoor variants behave identically across every endpoint and event type.

Status returns contact_detection.faulted, tamper_detection.detected, and sensor_reporting_state.value alongside the common sensor telemetry.

"contact_detection": {
  "faulted": true
},
"tamper_detection": {
  "detected": false
},
"sensor_reporting_state": {
  "value": "active"
}

The payload does not tell you what the sensor is attached to. A door and a window produce the same event from the same device shape, so store that mapping at setup time.

Flood/freeze sensor

The Ring flood/freeze sensor carries two independent detectors in one device. Status returns flood_detection.faulted, freeze_detection.faulted, tamper_detection.detected, and sensor_reporting_state.value alongside the common sensor telemetry.

"flood_detection": {
  "faulted": true
},
"freeze_detection": {
  "faulted": true
}
Important: Flood and freeze are independent. A device can report both detectors faulted at the same time, and clearing one does not clear the other. A freeze event implies nothing about the flood state, and the reverse. Read both attributes.

The 40 °F (4 °C) freeze threshold is fixed. It is not configurable, and the event payload carries no temperature reading.

Indoor temperature/humidity sensor

This sensor measures ambient temperature and relative humidity and emits an event when a reading crosses a threshold. It has no detection attributes, so status returns the common sensor telemetry plus temperature and humidity.

You can reach a reading two ways: poll status for the last reported value, or read it from the threshold event when a bound is crossed. Its configured bounds are readable under Sensor configuration.

Indoor air quality monitor

This monitor measures particulate matter (PM2.5), carbon monoxide, temperature, and humidity. It has no detection attributes, so status returns the common sensor telemetry plus temperature, humidity, pm2_5, and co.

Because it measures temperature and humidity as well as air quality, it emits the four temperature and humidity threshold events in addition to its own four. Nothing in the payload distinguishes this device from a temperature/humidity sensor, so map device IDs to device types on your side.

Temperature and humidity are measured against both an upper and a lower bound, so their events carry a threshold_type. PM2.5 and carbon monoxide are contaminants, measured against an upper limit only. A pm25_exceeded event therefore always means the level rose above its limit, and reports no direction.

Only the temperature and humidity bounds are readable under Sensor configuration.

PM2.5 readings arrive in the threshold event as pm25_level, which is the same measurement the status response calls pm2_5.

"attributes": {
  "source": "<device_id>",
  "source_type": "devices",
  "timestamp": 1787769360072,
  "pm25_level": 551.2
}

This device does not support battery reporting, so its battery_status.percentage reads 255 rather than a level.

Warning: Do not use co_exceeded as a life-safety trigger. Ring's own alarm and notification paths are the authority for carbon monoxide alerts, and this event is not a substitute for them.

Processing sensor events

One handler covers every sensor. Route on data.type, persist before processing, and read the reading only for threshold events.

READING_ATTRIBUTE = {
    'temperature': 'temperature',
    'humidity': 'humidity',
    'pm25': 'pm25_level',
}


def handle_sensor_event(payload):
    event_type = payload['data']['type']
    account_id = payload['meta']['account_id']
    request_id = payload['meta']['request_id']
    event_id = payload['data']['id']

    attributes = payload['data']['attributes']
    device_id = attributes['source']
    timestamp = attributes['timestamp']

    if already_processed(request_id):
        return {'status': 'duplicate'}

    # Persist first. A sensor event cannot be retrieved later.
    store_event(event_id, event_type, device_id, account_id, timestamp)

    measurement = event_type.rsplit('_', 1)[0]
    if measurement in READING_ATTRIBUTE:
        # Threshold event: the reading is in the payload, the threshold is not.
        reading = attributes[READING_ATTRIBUTE[measurement]]
        direction = attributes.get('threshold_type')
        notify_threshold_crossed(device_id, event_type, timestamp, reading, direction)
    else:
        # Detection event: the event type is the entire signal.
        notify_state_change(device_id, event_type, timestamp)

    return {'status': 'processed'}

Sensor best practices

Practice Detail
Verify the HMAC signature In X-Signature, before processing any payload.
Respond within 5 seconds With a 2xx, to avoid a delivery timeout.
Persist on receipt A sensor event you do not store cannot be recovered.
Deduplicate on meta.request_id Required rather than advisory for sensors.
Route on data.type Not on the presence of a state field or a reading.
Read the device ID from data.attributes.source Not by parsing data.id.
Poll status for authoritative state Especially after multiple near-simultaneous events.
Store your own device type and placement mapping The API provides neither.
Monitor battery_status.percentage From status, since no battery event exists. Check capabilities first, and ignore values outside 0–100.
Read reported_at with any reading Status returns the last reported value, not a live one.
Filter sensors out Before calling any media endpoint.

Chimes

Early Access: Chime support depends on dynamic scopes, which are in Early Access for developers. The endpoints, status attributes, configuration fields, and event types described in this section can change before general availability. You can integrate chime API calls into your app, but your app can only be published after dynamic scopes reach general availability for Ring customers.

A Ring Chime is a plug-in indoor speaker that announces events from the Ring cameras and doorbells on a user's account. A chime has no camera and no sensor. It is an output device: it plays audio, and it holds settings that determine what it plays and how loudly.

Chime settings are read-only to partners. Every chime operation available to your app is a read, with one exception: playback, which produces sound and changes nothing stored on the device. No partner operation mutates a chime's configuration.

Operation Available to partners
Read chime state: online, snooze Yes
Read chime settings: volume, audio slots Yes
Play audio on a chime Yes, with the Chime Controls capability. Changes no stored state.
Change volume or snooze No. The user controls these in the Ring app.
Change the audio in your app's slots Not through the device API. See Audio slots.
Live video, video download, snapshots No. A chime has no camera.

A chime is a devices resource, so the device endpoints you already use for cameras and doorbells work unchanged. What differs is the attribute set. Camera-oriented fields are present in the responses but always null, and the meaningful chime data sits under audio.

Chimes are not sensors. They report no physical condition, so nothing in Sensors applies to them. They are also not cameras, so the media streaming and video download endpoints described in Live Video and Media Clips do not apply.

Which devices this covers

Your app does not need to know which chime model it is talking to. A device accepts the audio your app plays when its capabilities report audio.customizable_slots greater than zero, and that is the only test your code should apply. See Identifying a chime.

For orientation, the Chimes scope group in the developer portal names two device types, and Ring sells both across more than one hardware generation.

Device type Description
Ring Chime Plug-in chime that announces doorbell presses and motion events. Supports selectable tones, snooze, and adjustable volume.
Ring Chime Pro Adds a wifi extender for nearby Ring devices and a built-in nightlight.

Treat those names as product context rather than as a list your app checks against. A model name does not tell you whether a given device has audio slots, and what a chime reports can differ between generations.

Note: The responses shown in this section were verified against a Ring Chime Pro. Treat the field set as the shape to expect on any chime, and check for null and missing attributes rather than assuming every chime reports identically.

Requesting chime access

Chime access is requested through the Chimes scope group when you create your app. See Configure Your Ring Application for where this sits in the app creation flow.

Selecting the Chimes group requests access to Ring Chime monitoring and control capabilities. On its own, the group covers chime device state and settings events.

Enabling the Chimes group reveals an Additional control capabilities toggle:

Capability Description
Chime Controls Play and change chime audio, ring tones, snoozes, and audio settings.

That description names the chime audio domain the scope covers. It is not a list of endpoints, and the four items in it reach you through three different mechanisms:

Portal phrase What your app does Mechanism
Play chime audio Plays audio on the chime POST /v1/devices/{device_id}/media/audio/playback
Change chime audio, ring tones Supplies the audio occupying your app's slots Set outside the device API. See Audio slots.
Snoozes Reads whether the user has snoozed the chime GET /v1/devices/{device_id}/status, read-only
Audio settings Reads the chime volume GET /v1/devices/{device_id}/configurations, read-only
Important: Only the first row is a command your app issues, and even that one changes no stored state. Reading the description as a promise of write endpoints for volume, ring tone, or snooze will send you looking for endpoints that do not exist.

Request Chime Controls only if your app plays audio on a chime. An app that reads chime state and settings to reflect them in a dashboard needs the Chimes group alone.

Warning: Plan your scopes carefully. You can update your scopes before submitting your app for certification. After certification, new scopes can be added but cannot be removed. To remove a scope, contact Ring Support.

Chime device discovery

Chimes are returned by the same call as your cameras and doorbells:

GET /v1/devices HTTP/1.1
Authorization: Bearer <access_token>

There is no chime-specific discovery endpoint. A chime appears in the devices collection with the standard relationships to status, capabilities, configurations, and location.

Add ?include=capabilities,configurations,status to sideload those resources and read the slot count, the slot contents, and the online and snooze state for every device in one round trip. The included resources arrive in a top-level included array, matched to each device by the type and id pair in its relationships. See Device Discovery for the matching rule.

{
  "data": [
    {
      "type": "devices",
      "id": "<device_id>",
      "attributes": {
        "name": "Upstairs",
        "image_url": "https://app-content.ring.com/shared/images/devices/square_device_images/ChimePro2/chimepro2_3x.png"
      },
      "relationships": {
        "location": {
          "data": { "type": "locations", "id": "<location_id>" },
          "links": { "related": "/v1/devices/<device_id>/location" }
        },
        "status": {
          "data": { "type": "device-status", "id": "<status_id>" },
          "links": { "related": "/v1/devices/<device_id>/status" }
        },
        "configurations": {
          "data": { "type": "device-configurations", "id": "<configurations_id>" },
          "links": { "related": "/v1/devices/<device_id>/configurations" }
        },
        "capabilities": {
          "data": { "type": "device-capabilities", "id": "<capabilities_id>" },
          "links": { "related": "/v1/devices/<device_id>/capabilities" }
        }
      }
    }
  ],
  "meta": {
    "time": "2026-08-17T17:11:12Z"
  }
}

Identifying a chime

The device resource carries no device type or device kind field. attributes contains only name and image_url, so there is no declared value you can switch on to recognize a chime.

Read the capabilities instead. A chime is the device whose audio.customizable_slots is greater than zero:

def plays_partner_audio(capabilities):
    audio = capabilities['data']['attributes'].get('audio') or {}
    return (audio.get('customizable_slots') or 0) > 0

On a device that is not a chime the audio block is absent, so customizable_slots is missing rather than 0. Both or defaults in the snippet are there for that case — without them the comparison raises an error on the first non-chime device in the collection.

Important: Do not infer device type from image_url. The path segment happens to name the hardware model, but it addresses a content asset and is not part of the API contract. Use audio.customizable_slots from the capabilities response.

Chime capabilities

GET /v1/devices/<device_id>/capabilities HTTP/1.1
Authorization: Bearer <access_token>

The response keeps the camera-oriented structure and nulls every field that does not apply, then describes the chime under audio.

{
  "data": {
    "type": "device-capabilities",
    "id": "<capabilities_id>",
    "attributes": {
      "video": {
        "configurations": null,
        "codecs": null,
        "ratio": null,
        "max_resolution": null,
        "supported_resolutions": null
      },
      "motion_detection": { "configurations": null },
      "image_enhancements": { "configurations": null },
      "audio": {
        "customizable_slots": 2
      }
    },
    "relationships": {
      "configurations": {
        "links": { "related": "/v1/devices/<device_id>/configurations" }
      },
      "data": {}
    }
  },
  "meta": {
    "time": "2026-08-17T17:11:12Z"
  }
}

Capability fields

  • audio.customizable_slots: The number of audio slots your app has on this chime, as an integer. Chime Pro reports 2. A value greater than zero is what identifies a device your app can play audio on.
  • video, motion_detection, image_enhancements: Always null on a chime. Present because the response shape is shared with cameras.
Note: audio.customizable_slots is an integer here, reporting how many slots exist. The same field name in the configurations response is an array holding the slots themselves. Read the capability value to size your interface and the configuration value to fill it. The name describes the slots, not your permissions: nothing your app calls changes which audio occupies them.

Chime configurations

GET /v1/devices/<device_id>/configurations HTTP/1.1
Authorization: Bearer <access_token>

Configurations hold the chime settings that determine what it plays and how loudly: the audio assigned to each slot, and the volume.

{
  "data": {
    "type": "device-configurations",
    "id": "<configurations_id>",
    "attributes": {
      "motion_detection": { "enabled": null },
      "audio": {
        "customizable_slots": [
          {
            "event": "ring-appstore-event-2",
            "disabled": false,
            "audio_ref": "4d4ec269-0bb2-4312-b7df-5e53c2e613e4",
            "audio_url": "https://ringtones-amz.ring.com/ring/appstore/9c62d803-6344-41a1-93c0-49f9331af83c.wav",
            "audio_name": "Ring Alert 3"
          },
          {
            "event": "ring-appstore-event-1",
            "disabled": false,
            "audio_ref": "6150f86e-2631-497c-bdef-1c5c777c10f7",
            "audio_url": "https://ringtones-amz.ring.com/ring/appstore/9fef0127-58fb-4f8e-ba87-29a85dc6db33.wav",
            "audio_name": "Navi"
          }
        ],
        "volume": 4
      },
      "image_enhancements": {
        "color_night_vision": null,
        "hdr": null,
        "ir_led_night_vision": null,
        "auto_zoom_track": null
      }
    }
  },
  "meta": {
    "time": "2026-08-17T17:11:13Z"
  }
}

Audio slots

Your app gets a fixed set of named slots on the chime, one per partner event. Each slot binds an event name to an audio file.

  • event: The slot identifier, of the form ring-appstore-event-<n>. Chime Pro exposes ring-appstore-event-1 and ring-appstore-event-2.
  • disabled: Whether the slot is suppressed. false means the slot plays. Skip a slot whose disabled is true when you choose audio to play.
  • audio_ref: Identifier for the audio assigned to the slot. This is the value the playback response echoes back.
  • audio_url: Absolute URL to the audio file, for preview in your own interface. Treat it as opaque. The host differs between the staging and production environments, so use the value as returned rather than constructing it.
  • audio_name: Display name of the assigned audio, for example Ring Alert 3.

Slots are not returned in event-name order. Sort by event before rendering them.

The slots belong to your integration. Their event names are namespaced to the Ring Appstore, the audio is served from Ring Appstore storage, and the capabilities response describes them as customizable. This is the part of the chime the Chime Controls description means by changing chime audio and ring tones.

Important: Assigning audio to a slot is not a device API operation. No endpoint on /v1/devices/{device_id} writes a slot, and the configurations endpoint is read-only for chimes. Your app reads which audio occupies each slot and plays it.
Which identifier to store

A slot keeps its event name when the audio in it changes. Store event names against your own application events, and resolve the audio_ref from configurations each time you play.

EVENT_SLOTS = {
    "door_open": "ring-appstore-event-1",
    "package_delivered": "ring-appstore-event-2",
}

def audio_ref_for(configurations, app_event):
    slots = configurations['data']['attributes']['audio']['customizable_slots']
    target = EVENT_SLOTS[app_event]
    for slot in slots:
        if slot['event'] == target and not slot['disabled']:
            return slot['audio_ref']
    return None
Important: Do not persist an audio_ref. It is the value the playback request takes, which makes it the tempting one to store, but it can change when a user re-runs device setup or removes your integration. A stored audio_ref goes stale with nothing to tell you; a stored event name does not.
Slot audio does not play by itself

The event name on a slot is a label your app gives meaning to, not a trigger Ring acts on. Nothing plays a slot's audio when the named event occurs, and your app cannot register it to. Audio in your slots is heard only when your app calls playback.

Note: A chime also announces doorbell presses and motion from the Ring devices on the account. Those announcements are Ring's own and are configured by the user in the Ring app. Your slots are not part of them.

Volume

audio.volume is the current chime volume as an integer. Chime Pro reported 4.

Note: The range and scale of audio.volume are not yet specified. Present it as the raw value the API returns rather than converting it to a percentage or a filled meter, so your interface stays correct when the range is published.

Configurations are read-only

There is no partner operation that writes chime configurations. Volume is changed by the user in the Ring app. Slot audio is set outside the device API, as described in Audio slots. Neither is reachable from an endpoint your app calls.

Two consequences worth designing for:

  • Treat every value here as a snapshot you display, not a setting you own. It can change at any time without your app being involved.
  • Re-read this endpoint when you need current values. See Chime events for the limits on learning about changes as they happen.

Chime status

GET /v1/devices/<device_id>/status HTTP/1.1
Authorization: Bearer <access_token>

Chime status is deliberately small. It reports reachability and snooze, and nothing else.

{
  "data": {
    "type": "device-status",
    "id": "<status_id>",
    "attributes": {
      "online": true,
      "audio": {
        "snooze": {
          "active": false,
          "until": null
        }
      }
    }
  },
  "meta": {
    "time": "2026-08-17T17:11:13Z"
  }
}

Status fields

  • online: Whether the chime is reachable.
  • audio.snooze.active: Whether the user has snoozed the chime. A snoozed chime suppresses its announcements.
  • audio.snooze.until: When the snooze expires. Null when active is false.

Snooze is set by the user in the Ring app. No partner operation snoozes or unsnoozes a chime, so treat audio.snooze as a condition to respect rather than one to manage.

Volume is not a status attribute. Read it from Chime configurations.

Note: A chime status response carries no battery_status and no signal_strength. Chimes are mains-powered, so there is no battery to report. Do not reuse sensor status handling that expects those attributes. Chime Pro nightlight state is not exposed through the partner API.

Playing audio on a chime

Playback is the only chime command available to partners, and it is gated by the Chime Controls capability. It plays audio; it changes nothing about the device.

POST /v1/devices/<device_id>/media/audio/playback HTTP/1.1
Authorization: Bearer <access_token>
Content-Type: application/json

Request

Name the audio to play with its audio_ref, in a JSON:API document whose data.type is audio.

{
  "data": {
    "type": "audio",
    "attributes": {
      "audio_ref": "4d4ec269-0bb2-4312-b7df-5e53c2e613e4"
    }
  }
}

Request fields

Field Type Required Description
data.type String Yes Always audio
data.attributes.audio_ref String Yes The audio_ref of the audio to play, read from audio.customizable_slots in the device configurations

The request carries no slot or event name. Playback addresses the audio directly by audio_ref, so read Chime configurations first and take the audio_ref of the slot you want to sound.

Playing the audio in a slot

  1. GET /v1/devices/{device_id}/configurations and find the slot you want in audio.customizable_slots, matching on event. Skip it if disabled is true.
  2. POST /v1/devices/{device_id}/media/audio/playback with that slot's audio_ref.

Step 1 is required: the audio_ref the request needs has no other source. Two further reads are worth making, and neither is a precondition the request enforces.

  • GET /v1/devices/{device_id}/capabilities confirms audio.customizable_slots is greater than zero, which is how you tell a chime from a camera while working through a mixed device collection. If you already hold a known chime's device_id, you can skip it.
  • GET /v1/devices/{device_id}/status reports online and audio.snooze.active. Check both so your app does not try to sound a chime the user has silenced or unplugged.

Fetch all three in one request with GET /v1/devices/{device_id}?include=capabilities,configurations,status.

Response (200 OK)

{
  "data": {
    "type": "audio",
    "id": "4d4ec269-0bb2-4312-b7df-5e53c2e613e4",
    "attributes": {
      "status": "completed"
    }
  },
  "meta": {
    "time": "2026-08-17T17:11:14.002266998Z"
  }
}

The response is synchronous. data.attributes.status of completed means the chime played the audio, and data.id echoes back the audio_ref you sent, so you can correlate the response with the request that caused it.

Note: Pass an audio_ref your app already holds in one of its slots, read from the configurations response. Whether the endpoint accepts an audio_ref that is not currently assigned to one of your slots is not specified.

Chime events

A chime delivers the standard device lifecycle events. These are the same event types documented for cameras and doorbells, with the chime as the event source.

Event data.type Details
Device addition device_added Device Addition
Device removal device_removed Device Removal

A device_added event for a chime uses the standard v1.1 envelope:

{
  "meta": {
    "version": "1.1",
    "time": "2026-08-16T22:44:20.294608731Z",
    "request_id": "<request_id>",
    "account_id": "<account_id>"
  },
  "data": {
    "id": "<device_id>_device_added_1786920259106",
    "type": "device_added",
    "attributes": {
      "source": "<device_id>",
      "source_type": "devices",
      "timestamp": 1786920259106
    },
    "relationships": {
      "devices": {
        "links": {
          "self": "/v1/devices/<device_id>"
        }
      }
    }
  }
}

Chime webhooks arrive on the same delivery path as every other Ring webhook, with an HMAC-SHA256 signature in the X-Signature header that you must verify before processing the payload. See Notifications for signature verification.

Important: No chime-specific event type is available yet. The two lifecycle events above are the only webhooks a chime delivers today, so there is no event telling you that a user changed chime volume or slot audio. Re-read the configurations and status endpoints when your app needs current values.

Best practices

  1. Detect chimes by capability: Switch on audio.customizable_slots being greater than zero, never on image_url or device name.
  2. Size slots from capabilities, read them from configurations: Read the integer audio.customizable_slots to learn how many slots exist, then read the array of the same name in configurations to see what occupies them.
  3. Sort slots before display: Slots are not returned in event-name order.
  4. Treat settings as read-only: Volume, slot audio, and snooze belong to the user. Present them as current state, and do not build an interface that implies your app can change them.
  5. Re-read rather than cache indefinitely: Chime settings can change at any time with no event to tell you. Refresh configurations and status when your app next needs accurate values, for example when a user opens the relevant screen.
  6. Treat playback as user-visible: A playback call produces sound in a user's home. Gate it behind an explicit user action or a clearly disclosed automation, never behind a retry loop or a polling side effect.
  7. Respect snooze: Check audio.snooze.active before playing audio. A snoozed chime reflects a deliberate user choice, and no partner operation overrides it.
  8. Check online first: A chime that is not online cannot play audio.
  9. Do not reuse sensor status handling: Chime status carries no battery_status and no signal_strength.
  10. Verify HMAC signature: Always verify the X-Signature header on chime webhooks before processing.
  11. Store event names, not audio_ref values: A slot keeps its event name when its audio changes. Resolve the audio_ref from configurations at the point of playback.
  12. Nothing plays your slot audio but you: A slot's event name is your label, not a trigger. Your audio sounds only when your app calls playback.
  13. Expect change during Early Access: Chime endpoints, fields, and event types can change before general availability. Isolate chime handling behind an interface you can revise.

Event History

The Event History API allows partners to retrieve past events for a specific device. Events include motion detections, live view sessions, doorbell presses, and other device activity. This endpoint returns event metadata only — use the Media Clips and Image Snapshots endpoints to access video clips or images for specific events.

See also: Notifications for real-time event delivery via webhooks.

Overview

[Page truncated to keep GeezerVison snappy.]

Method Endpoint Purpose
GET https://api.amazonvision.com/v1/history/devices/{device_id}/events
You are visitor 01402503 — GeezerVison does not store the pages you look up.
Best viewed with Netscape Navigator 4.7 or Internet Explorer 6 at 800×600. No cookies required.