Webhooks

Webhooks are how Versa clients consume asynchronous events. Receivers use webhooks to be notified of new encrypted data and customer lifecycle events, and senders use webhooks to track the progress of receipt retrievals they have requested.

Supported Events

Receiver events

EventDescription
receiptThe original encrypted payload. Existing senders use this event at the protocol level.
receipt.decryptedThe event received by custodial clients at their remote endpoint.
itineraryThe encrypted payload for an itinerary event, which excludes payment data.
itinerary.decryptedAs above, the custodial event for itineraries.

Sender events

These events correspond to the lifecycle of a receipt retrieval requested via the Receipt Retrieval API.

EventDescription
receipt_retrieval.scheduledA receipt retrieval process has been scheduled for a transaction.
receipt_retrieval.initiatedA receipt retrieval has started running for a transaction.
receipt_retrieval.completedA receipt was successfully attached to the transaction.
receipt_retrieval.failedThe receipt retrieval process for a transaction concluded unsuccessfully and will not be automatically retried.

Using Webhooks

On the Versa network, receipt data is sent directly from verified merchants to receivers. That means that the receipt event is posted to receivers by sending clients, not by the Versa registry. All receivers use webhooks to handle encrypted data sent over the protocol, and custodial clients use separate webhooks to get deliveries from Versa servers.

Custodial Webhooks

Custodial clients subscribe to a receipt.decrypted event. The custodial service receives and decrypts data on your behalf, and then posts a separate webhook event to your remote endpoint, in the following format:

{
  "event": "receipt.decrypted",
  "event_id": "evt_6abf1062dc2f4844a81b645b9a5dbf41",
  "event_at": 1739315358,
  "delivery_id": "whd_809aba728d6940de8e294af8b3ad272c",
  "delivery_at": 1739315358,
  "data": {
    "receipt": { ... },
    "checkout": {
      "sender": {
        "brand_color": "#f0C14B",
        "logo": "https://imagedelivery.net/image_url.png",
        "name": "Supplier Co.",
        "org_id": "org_b6d073a9bf3a4c49a8c03191f87d8ee2",
        "website": "supplier.com"
      },
      "handles": {
        "customer_email": "john.smith@example.com",
        "customer_email_domain": "example.com"
      },
      "receipt_id": "rct_14a9817fea7d41bf941c16b8750cc845",
      "registered_at": 1739315357,
      "schema_version": "2.3.0",
      "transaction_id": "txn_6b408ba7406b4754b7b7d287e0ca9fdf",
      "transaction_event_index": 0
    }
  }
}

Receipt Retrieval Events

Senders who use the Receipt Retrieval API can subscribe to receipt retrieval events to track the progress of each attempted retrieval. Every event in this family carries the Versa transaction_id returned when you posted the event and the merchant_transaction_id you supplied with it — the same two ids the Flags API accepts.

The receipt_retrieval.scheduled and receipt_retrieval.initiated events share the following shape:

{
  "event": "receipt_retrieval.scheduled",
  "event_id": "evt_6abf1062dc2f4844a81b645b9a5dbf41",
  "event_at": 1739315358,
  "delivery_id": "whd_809aba728d6940de8e294af8b3ad272c",
  "delivery_at": 1739315358,
  "data": {
    "transaction_id": "txn_555raszzb2esgvra33qn9kyw6t",
    "merchant_transaction_id": "your_booking_id"
  }
}

The receipt_retrieval.completed event is sent each time a receipt is attached to the transaction. It carries every receipt you can see on the transaction, a signed URL for each, and what became of the booking:

{
  "event": "receipt_retrieval.completed",
  "event_id": "evt_6abf1062dc2f4844a81b645b9a5dbf41",
  "event_at": 1789776000,
  "delivery_id": "whd_809aba728d6940de8e294af8b3ad272c",
  "delivery_at": 1789776000,
  "data": {
    "transaction_id": "txn_555raszzb2esgvra33qn9kyw6t",
    "merchant_transaction_id": "your_booking_id",
    "receipt_file_url": "https://storage.googleapis.com/...",
    "receipts": [
      {
        "file_id": "file_01kxb5d7qve00a78ns2gqj6xqs",
        "url": "https://storage.googleapis.com/...",
        "size": 55555,
        "content_type": "application/pdf",
        "created": 1789776000
      }
    ],
    "booking_disposition": "completed"
  }
}

Completed event fields

FieldTypeDescription
receiptsarrayEvery receipt on the transaction, newest first, including the one that triggered this event. Each item is a file object (below).
receipts[].file_idstringThe file's id. Use it with the Files API to fetch a fresh URL or the extracted receipt data at any later time. A receipt Versa collected from your corporate card provider is listed here only when it triggered the event; the Files API answers 404 for it, so save it from url while the link is valid.
receipts[].urlstringA signed download URL, valid for 15 minutes from delivery. Retries carry freshly signed URLs.
receipts[].sizeintegerFile size in bytes.
receipts[].content_typestringThe file's MIME type, for example application/pdf.
receipts[].createdintegerWhen the file was stored, in Unix seconds.
receipt_file_urlstringThe url of the receipt that triggered this event. Kept for integrations that predate receipts; new integrations should read receipts.
booking_dispositionnullable enumcompleted, canceled or no_show: what became of the booking as far as Versa knows when the receipt arrived. null if Versa could not determine it; treat as unknown.

A transaction that receives a second receipt (a corrected folio, say) fires a second receipt_retrieval.completed whose receipts lists both.

To read the same facts on demand, look the transaction up with the Transactions API.

And receipt_retrieval.failed includes a failure_code.

{
  "event": "receipt_retrieval.failed",
  "event_id": "evt_6abf1062dc2f4844a81b645b9a5dbf41",
  "event_at": 1739315358,
  "delivery_id": "whd_809aba728d6940de8e294af8b3ad272c",
  "delivery_at": 1739315358,
  "data": {
    "transaction_id": "txn_555raszzb2esgvra33qn9kyw6t",
    "merchant_transaction_id": "your_booking_id",
    "failure_code": "no_show"
  }
}

Failure Codes

The following are our currently supported receipt retrieval failure codes. This list may be updated or expanded; it is not guaranteed to be exhaustive.

CodeDescription
booking_canceledThe reservation was canceled prior to check-in.
no_responseThe property did not respond to retrieval attempts.
no_showThe customer did not show up for the booking.
property_could_not_findThe property was unable to locate the booking.
property_refusedThe property declined to provide the receipt.
skip_permanentlyVersa stopped retrieval for this transaction; it was not worth pursuing (for example, a zero-amount charge).
third_party_billingThe stay was billed to a third party.

API Reference

You can create, list, update, and delete your webhooks, and you can rotate your webhook secrets at any time (remember to then update your destination endpoint with the new secret).

All webhook endpoints require Basic Authentication with your client credentials.

  • POST /webhook - Create a new webhook
  • GET /webhook - List all webhooks for your client
  • PUT /webhook/:id - Update the endpoint_url or events for a specific webhook
  • PUT /webhook/:id/rotate - Rotate the secret for a specific webhook
  • DELETE /webhook/:id - Delete a specific webhook