Orvelt
API reference

Webhook operations

Open a delivery portal and create or update webhook destinations.

Webhook operations require the webhooks permission.

Events

Topics are named <resource>.<past_tense_verb>. Every delivery is a JSON POST with the same envelope, which follows the Standard Webhooks payload shape:

{
  "id": "01926f3a-8a1e-7c52-9d0b-3c1f6e2a4b10",
  "type": "opinion.created",
  "timestamp": "2026-09-26T14:03:11.482Z",
  "data": { "reviewId": "…", "projectId": "…" }
}

id is unique per event and stays the same when a delivery is retried, so use it to make processing idempotent. timestamp is when the change happened. data holds the resource as it was after the change.

Opinions

TopicSent when
opinion.createdA customer submits an opinion. data includes the title, the opinion text, the store, and the customer (anonymous customers have no id). Each answer they gave arrives as its own answer.created.
opinion.updatedA customer edits a submitted opinion. data is the full opinion plus changes, which lists title and/or opinion.
opinion.deletedA customer deletes a submitted opinion, along with its answers. data contains reviewId and projectId.

Answers

Every answer to a project question is sent separately, whether it was given with an opinion or through a widget. data includes answerId, reviewId, questionId, the question text and its tags, the answer, any selectedOptions, and the customer.

TopicSent when
answer.createdA customer answers a project question.
answer.updatedA customer changes their answer.

Comments

TopicSent when
comment.createdA customer comments on a review, or your team responds to one. data.author.type is customer or business.
comment.updatedA comment or team response is edited.
comment.deletedA customer deletes their comment. data contains commentId, reviewId, and projectId.

Verifications

Verification events share one payload: verificationId, projectId, reviewId, method (visual_evidence, delivery_link, qr_permanent, or qr_rotating), status, previousStatus, and occurredAt. Receipt images and extracted evidence are never included. Internal processing and manual-review steps are not emitted as separate webhook topics; verification.failed uses status to distinguish rejected, expired, and cancelled.

TopicSent when
verification.createdA verification starts. status is the status it starts in.
verification.verifiedThe purchase is verified.
verification.failedThe verification did not complete successfully. data.status is rejected, expired, or cancelled.

Delivery link events share one payload, a snapshot of the link after the change: verificationId, projectId, customerEmail, customerReference, status (issued, clicked, submitted, or expired), reviewId, issuedAt, expiresAt, emailedAt, clickedAt, and submittedAt. The link URL is never included, because it grants access to the opinion form. reviewId is set only once a customer submits a named opinion with the link; incognito opinions are never connected to it.

TopicSent when
link.createdA delivery link is created for a customer.
link.emailedA delivery link is emailed to its customer. Sent each time it is emailed.
link.clickedThe customer opens the link and starts an opinion with it. Sent once per link.
link.submittedThe customer submits an opinion with the link, which verifies it. The matching verification.verified is sent too.

Subscribers

TopicSent when
subscriber.createdA customer subscribes to a project, directly or by accepting an invitation.
subscriber.deletedA customer unsubscribes, or your team removes them. data.blocked is true when your team removed them.

Projects, questions, and QR codes

TopicSent when
project.created, project.updated, project.deletedA project is created, changed (name, slug, description, status, or settings), or deleted.
question.created, question.updated, question.deletedA project question is added, edited, activated or archived, or removed.
qr_code.created, qr_code.regenerated, qr_code.revokedAn opinion QR code is created, regenerated (invalidating printed copies), or revoked.

webhooks.portal

Open portal request

POST /webhooks/portal

import { webhooks } from '@orvelt/business';

const result = await webhooks.portal({
  theme: 'dark',
});

if (result.error) throw new Error(result.error.message);
console.log(result.data);
curl --request POST \
  --url "$ORVELT_API_BASE_URL/webhooks/portal" \
  --header "x-api-key: $ORVELT_BUSINESS_API_KEY" \
  --header "content-type: application/json" \
  --data '{"theme":"dark"}'

theme accepts light or dark.

webhooks.destinations.upsert

Save destination request

POST /webhooks/destinations

import { webhooks } from '@orvelt/business';

const result = await webhooks.destinations.upsert({
  url: 'https://example.com/webhooks/orvelt',
  topics: ['opinion.created', 'answer.created', 'verification.verified'],
});

if (result.error) throw new Error(result.error.message);
console.log(result.data);
curl --request POST \
  --url "$ORVELT_API_BASE_URL/webhooks/destinations" \
  --header "x-api-key: $ORVELT_BUSINESS_API_KEY" \
  --header "content-type: application/json" \
  --data '{"url":"https://example.com/webhooks/orvelt","topics":["opinion.created","answer.created","verification.verified"]}'

Include destinationId to update an existing destination. url is required; topics can contain any non-empty subset of the supported topics. Leave topics out to receive every topic.