ReviewNudger API for Zapier and webhooks

Last updated: July 2, 2026

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.

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.

ScopeGrants
review_requests:writeCreate review requests via payment_completed events.
contacts:writeCreate or update customer records via customer_created events.
metadata:readRead connection details (account and location names).

GET /oauth/authorize

Starts the authorization flow. The user signs in, reviews the requested scopes, and picks the account and location to connect.

Query parameterRequiredDescription
response_typeYesMust be code.
client_idYesThe OAuth client identifier. The Zapier integration uses zapier.
redirect_uriYesMust exactly match a redirect URI pre-registered for the client.
scopeYesSpace-separated scopes from the table above.
stateYesOpaque client value echoed back on the redirect for CSRF protection.
code_challengeYesPKCE code challenge derived from the code verifier.
code_challenge_methodYesMust 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

Both 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.

FieldTypeRequiredDescription
eventTypestringYesMust be payment_completed.
receiptIdstringYesStable 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.
occurredAtstringYesISO-8601 timestamp of when the payment or transaction completed.
paymentIdstringYesThe payment, order, job, or transaction ID from the source system.
amountstring or numberYesTransaction amount as a non-negative decimal with at most 2 fraction digits, for example 125.50.
currencystringYesThree-letter ISO currency code, for example USD.
customerEmailstringOne of email/phoneCustomer email address. At least one of customerEmail or customerPhone is required.
customerPhonestringOne of email/phoneCustomer phone number. At least one of customerEmail or customerPhone is required.
customerNamestringNoCustomer full name.
customerFirstNamestringNoCustomer first name, if the source system provides it separately.
customerLastNamestringNoCustomer last name, if the source system provides it separately.
metadataobjectNoOptional 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

OK

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.

FieldTypeRequiredDescription
eventTypestringYesMust be customer_created.
receiptIdstringYesStable unique ID for the source event (for example the lead, form entry, or CRM record ID). This is the idempotency key.
occurredAtstringYesISO-8601 timestamp of when the contact was created in the source system.
customerEmailstringOne of email/phoneCustomer email address. At least one of customerEmail or customerPhone is required.
customerPhonestringOne of email/phoneCustomer phone number. At least one of customerEmail or customerPhone is required.
customerNamestringNoCustomer full name.
customerFirstNamestringNoCustomer first name, if the source system provides it separately.
customerLastNamestringNoCustomer last name, if the source system provides it separately.
customerIdstringNoOptional customer ID from the source system, stored for reference. It is not used to match customers.
metadataobjectNoOptional 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.

Idempotency and retries

  • receiptId is 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 without creating anything new. A replayed customer_created returns the same customerId as the original request.
  • One payment_completed event 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.

StatusMeaning
HTTP 400Validation 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 401Missing, invalid, expired, or revoked access token. The response includes WWW-Authenticate: Bearer. Refresh the access token and retry.
HTTP 403The token is valid but missing the required scope. The response includes WWW-Authenticate: Bearer error="insufficient_scope". Reauthorize with the full scope set.
HTTP 429Rate limit exceeded on the OAuth endpoints. Retry after the indicated delay.
HTTP 500Processing failure on our side. Safe to retry with the same receiptId; the replay collapses onto the original event.