This page documents the API behind the ReviewNudger™ Zapier integration and the authenticated custom webhook. All endpoints are served from https://www.reviewnudger.com over HTTPS. Requests and responses use JSON unless noted otherwise. Questions: support@reviewnudger.com.
Integrating directly — from Make.com, Integrately, n8n, or your own code — without the Zapier OAuth flow? Use the ReviewNudger API documentation instead, which covers API-key authentication.
Authentication: OAuth 2.0 with PKCE
The API uses the OAuth 2.0 Authorization Code grant with PKCE (RFC 7636, S256 only). Clients are public clients: there is no client secret, and PKCE is required on every authorization. The Zapier integration uses the client ID zapier.
Each authorization is bound to exactly one ReviewNudger account and one business location, chosen by the user on the consent screen. Issued tokens carry that binding: requests made with the token always act on that account and location.
| Scope | Grants |
|---|---|
| review_requests:write | Create review requests via payment_completed and appointment_completed events. |
| contacts:write | Create or update customer records via customer_created events. |
| metadata:read | Read connection details (account and location names). |
| reviews:read | Read synced public reviews from the new-review trigger feed. |
| feedback:read | Read private customer feedback from the new-feedback trigger feed. |
| review_requests:read | Read send activity from the review-request-sent trigger feed. |
GET /oauth/authorize
Starts the authorization flow. The user signs in, reviews the requested scopes, and picks the account and location to connect.
| Query parameter | Required | Description |
|---|---|---|
| response_type | Yes | Must be code. |
| client_id | Yes | The OAuth client identifier. The Zapier integration uses zapier. |
| redirect_uri | Yes | Must exactly match a redirect URI pre-registered for the client. |
| scope | Yes | Space-separated scopes from the table above. |
| state | Yes | Opaque client value echoed back on the redirect for CSRF protection. |
| code_challenge | Yes | PKCE code challenge derived from the code verifier. |
| code_challenge_method | Yes | Must be S256. Plain PKCE is not supported. |
On success the user is redirected to redirect_uri with code and state query parameters. The authorization code is single-use and expires after 10 minutes. Recoverable errors are returned on the redirect as error and error_description parameters (for example invalid_scope, unsupported_response_type, or access_denied when the user declines). Requests with an unknown client or unregistered redirect URI are rejected without redirecting.
POST /oauth/token
Exchanges an authorization code for tokens, or refreshes an access token. The body must be application/x-www-form-urlencoded.
Grant: authorization_code
POST /oauth/token Content-Type: application/x-www-form-urlencoded grant_type=authorization_code &code=<authorization code> &redirect_uri=<same redirect_uri as the authorize request> &client_id=zapier &code_verifier=<PKCE code verifier>
Grant: refresh_token
POST /oauth/token Content-Type: application/x-www-form-urlencoded grant_type=refresh_token &refresh_token=<current refresh token> &client_id=zapier
Success response (200)
{
"access_token": "rndg_oat_...",
"token_type": "Bearer",
"expires_in": 3600,
"refresh_token": "rndg_ort_...",
"scope": "review_requests:write contacts:write metadata:read",
"account_id": "<account UUID>",
"location_id": "<location UUID>",
"business_name": "Acme Plumbing",
"location_name": "Downtown"
}Access tokens expire after 1 hour. Refresh tokens expire after 60 days and rotate on every refresh: each refresh response contains a new refresh token and invalidates the previous one. Presenting an already-rotated refresh token is treated as token theft and revokes the entire grant, after which the user must reauthorize.
Errors are returned as JSON with error and error_description: HTTP 400 for invalid_request, invalid_grant, and unsupported_grant_type; HTTP 401 for invalid_client. OAuth endpoints are rate limited and return HTTP 429 when the limit is exceeded.
POST /oauth/revoke
Revokes a grant (RFC 7009). The form body takes token (required; an access token or refresh token) and an optional token_type_hint of access_token or refresh_token. Revoking either token revokes the entire grant. The endpoint returns HTTP 200 with an empty body, including for unknown tokens.
GET /api/zapier/oauth-connection-check
Verifies a connection. Send the access token as Authorization: Bearer <access_token>. Requires the metadata:read scope.
{
"status": "connected",
"accountId": "<account UUID>",
"accountName": "Acme Plumbing",
"locationId": "<location UUID>",
"locationName": "Downtown",
"connectionLabel": "Acme Plumbing — Downtown"
}Returns HTTP 401 for a missing, expired, or revoked token and HTTP 403 when the token lacks the scope, both with a WWW-Authenticate header as described under Errors.
POST /webhook/custom
All Zapier actions post to this endpoint. Send the access token as Authorization: Bearer <access_token> with Content-Type: application/json. The eventType field selects the action. The target account and location come from the access token; any accountId or locationId in the body is ignored.
Create review request — eventType payment_completed
Records a completed payment and, subject to the location's automation settings, creates and schedules one review request for the customer. Requires the review_requests:write scope. amount and currency are optional, but must be sent together. Leave both out when a trigger app exposes only minor units such as cents.
| Field | Type | Required | Description |
|---|---|---|---|
| eventType | string | Yes | Must be payment_completed. |
| receiptId | string | Yes | Stable unique ID for the source event (for example the payment or order ID). This is the idempotency key: replays with the same receiptId do not create duplicates. |
| occurredAt | string | Yes | ISO-8601 timestamp of when the payment or transaction completed. |
| paymentId | string | Yes | The payment, order, job, or transaction ID from the source system. |
| amount | string or number | No | Optional transaction amount in major currency units as a non-negative decimal with at most 2 fraction digits, for example 125.50. Provide together with currency or leave both out. |
| currency | string | No | Three-letter ISO currency code, for example USD. Required when amount is provided. |
| customerEmail | string | One of email/phone | Customer email address. At least one of customerEmail or customerPhone is required. |
| customerPhone | string | One of email/phone | Customer phone number. At least one of customerEmail or customerPhone is required. |
| customerName | string | No | Customer full name. |
| customerFirstName | string | No | Customer first name, if the source system provides it separately. |
| customerLastName | string | No | Customer last name, if the source system provides it separately. |
| metadata | object | No | Optional JSON object with key-value pairs stored for troubleshooting. |
POST /webhook/custom
Authorization: Bearer rndg_oat_...
Content-Type: application/json
{
"eventType": "payment_completed",
"receiptId": "pi_3P8example",
"occurredAt": "2026-07-02T14:00:00Z",
"paymentId": "pi_3P8example",
"amount": 125.50,
"currency": "USD",
"customerEmail": "customer@example.com",
"customerName": "Jordan Lee"
}
HTTP/1.1 200 OK
Content-Type: application/json
{
"status": "review_request_created",
"reviewRequestCreated": true,
"reviewRequestId": "<review request UUID>",
"sourceEventId": "<source event UUID>"
}Create review request — eventType appointment_completed
Records a completed appointment from a scheduler or booking tool and creates one review request under the same automation settings as payments. Identical to payment_completed except the business ID field is appointmentId and amount/currency are optional (send both or neither). Requires the review_requests:write scope.
| Field | Type | Required | Description |
|---|---|---|---|
| eventType | string | Yes | Must be appointment_completed. |
| receiptId | string | Yes | Stable unique ID for the source event (for example the appointment or booking ID). This is the idempotency key: replays with the same receiptId do not create duplicates. |
| occurredAt | string | Yes | ISO-8601 timestamp of when the appointment was completed. Use the completion or end time, not the booking time, so the request follows the visit. |
| appointmentId | string | Yes | The appointment or booking ID from the source system. |
| amount | string or number | No | Optional appointment value as a non-negative decimal with at most 2 fraction digits. Provide together with currency or leave both out. |
| currency | string | No | Three-letter ISO currency code, for example USD. Required when amount is provided. |
| customerEmail | string | One of email/phone | Customer email address. At least one of customerEmail or customerPhone is required. |
| customerPhone | string | One of email/phone | Customer phone number. At least one of customerEmail or customerPhone is required. |
| customerName | string | No | Customer full name. |
| customerFirstName | string | No | Customer first name, if the source system provides it separately. |
| customerLastName | string | No | Customer last name, if the source system provides it separately. |
| metadata | object | No | Optional JSON object with key-value pairs stored for troubleshooting. |
POST /webhook/custom
Authorization: Bearer rndg_oat_...
Content-Type: application/json
{
"eventType": "appointment_completed",
"receiptId": "appt_88213",
"occurredAt": "2026-07-02T16:30:00Z",
"appointmentId": "appt_88213",
"customerPhone": "+15555550123",
"customerName": "Jordan Lee"
}
HTTP/1.1 200 OK
Content-Type: application/json
{
"status": "review_request_created",
"reviewRequestCreated": true,
"reviewRequestId": "<review request UUID>",
"sourceEventId": "<source event UUID>"
}Review-request action responses report the actual outcome. A disabled rule or cooldown returns status: "suppressed", reviewRequestCreated: false, and a suppressionReason. A safe replay returns status: "already_processed". These are successful HTTP 200 outcomes because the event was accepted and will not be retried into a duplicate.
Create or update contact — eventType customer_created
Creates a customer record, or updates the existing one when conservative matching by email or phone finds it, without sending a review request. Requires the contacts:write scope.
| Field | Type | Required | Description |
|---|---|---|---|
| eventType | string | Yes | Must be customer_created. |
| receiptId | string | Yes | Stable unique ID for the source event (for example the lead, form entry, or CRM record ID). This is the idempotency key. |
| occurredAt | string | Yes | ISO-8601 timestamp of when the contact was created in the source system. |
| customerEmail | string | One of email/phone | Customer email address. At least one of customerEmail or customerPhone is required. |
| customerPhone | string | One of email/phone | Customer phone number. At least one of customerEmail or customerPhone is required. |
| customerName | string | No | Customer full name. |
| customerFirstName | string | No | Customer first name, if the source system provides it separately. |
| customerLastName | string | No | Customer last name, if the source system provides it separately. |
| customerId | string | No | Optional customer ID from the source system, stored for reference. It is not used to match customers. |
| metadata | object | No | Optional JSON object with key-value pairs stored for troubleshooting. |
POST /webhook/custom
Authorization: Bearer rndg_oat_...
Content-Type: application/json
{
"eventType": "customer_created",
"receiptId": "lead_84213",
"occurredAt": "2026-07-02T14:00:00Z",
"customerEmail": "customer@example.com",
"customerFirstName": "Jordan",
"customerLastName": "Lee"
}
HTTP/1.1 200 OK
Content-Type: application/json
{
"status": "ok",
"customerId": "<ReviewNudger customer UUID>"
}customerId in the response is the ReviewNudger UUID of the created or matched customer.
Other event types
Authenticated requests with an unsupported eventType return HTTP 200 with the body Ignored unsupported custom event type. and have no effect.
Trigger feeds
The Zapier triggers poll three read-only feeds. Send the access token as Authorization: Bearer <access_token>. Each feed returns a JSON array (no envelope) of up to 100 items, newest first, scoped to the account and location bound to the token. Every item carries a stable id that pollers should deduplicate on. Fields whose value is unknown are null. Authentication failures return HTTP 401 and missing scopes return HTTP 403, both as described under Errors.
GET /api/zapier/triggers/new-review
Synced public reviews for the connected location. Requires the reviews:read scope. Items are ordered by first-synced time.
[
{
"id": "<review UUID>",
"provider": "google",
"externalReviewId": "<review ID on the review site>",
"reviewerName": "Jordan Lee",
"starRating": 5,
"reviewText": "Fast, friendly, and the final price matched the quote.",
"reviewCreatedAt": "2026-04-22T14:00:00.000Z",
"firstSyncedAt": "2026-04-22T14:05:00.000Z",
"locationId": "<location UUID>",
"locationName": "Downtown"
}
]GET /api/zapier/triggers/new-feedback
Private feedback submitted by customers from review requests. Requires the feedback:read scope. Items are ordered by submission time.
[
{
"id": "<feedback UUID>",
"message": "The technician was great, but the invoice took a week to arrive.",
"submittedAt": "2026-04-22T15:30:00.000Z",
"reviewRequestId": "<review request UUID>",
"customerId": "<customer UUID>",
"customerName": "Jordan Lee",
"customerEmail": "customer@example.com",
"customerPhone": "+15551234567",
"locationId": "<location UUID>",
"locationName": "Downtown"
}
]GET /api/zapier/triggers/review-request-sent
Review-request send attempts (text or email) for the connected location. Requires the review_requests:read scope. Items are ordered by send time.
[
{
"id": "<delivery UUID>",
"channel": "sms",
"provider": "twilio",
"destination": "+15551234567",
"templateVersion": "review_request_v1",
"status": "sent",
"sentAt": "2026-04-22T14:10:00.000Z",
"reviewRequestId": "<review request UUID>",
"customerId": "<customer UUID>",
"customerName": "Jordan Lee",
"locationId": "<location UUID>",
"locationName": "Downtown"
}
]Idempotency and retries
receiptIdis the idempotency key, scoped per account. Use a stable ID from the source system (payment ID, lead ID) and reuse it on every retry of the same event.- Replaying a request that was already processed returns HTTP 200 with
status: "already_processed"without creating anything new. A replayedcustomer_createdreturns the samecustomerIdas the original request. - One
payment_completedorappointment_completedevent creates at most one review request, enforced by a unique constraint on the normalized event. - After an HTTP 500, retry with the same
receiptId: the retry resumes processing of the original event and cannot double-send.
Errors
OAuth endpoints return JSON error bodies with error and error_description. The webhook endpoint returns plain-text error bodies. Webhook responses also include an X-RN-Auth-Method header identifying how the request was authenticated.
| Status | Meaning |
|---|---|
| HTTP 400 | Validation failure. The plain-text body names the field, for example "occurredAt must be a valid ISO-8601 timestamp." Do not retry without fixing the payload. |
| HTTP 401 | Missing, invalid, expired, or revoked access token. The response includes WWW-Authenticate: Bearer. Refresh the access token and retry. |
| HTTP 403 | The token is valid but missing the required scope. The response includes WWW-Authenticate: Bearer error="insufficient_scope". Reauthorize with the full scope set. |
| HTTP 429 | Rate limit exceeded on the OAuth endpoints. Retry after the indicated delay. |
| HTTP 500 | Processing failure on our side. Safe to retry with the same receiptId; the replay collapses onto the original event. |