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
| Event | Description |
|---|---|
receipt | The original encrypted payload. Existing senders use this event at the protocol level. |
receipt.decrypted | The event received by custodial clients at their remote endpoint. |
itinerary | The encrypted payload for an itinerary event, which excludes payment data. |
itinerary.decrypted | As above, the custodial event for itineraries. |
Sender events
These events correspond to the lifecycle of a receipt retrieval requested via the Receipt Retrieval API.
| Event | Description |
|---|---|
receipt_retrieval.scheduled | A receipt retrieval process has been scheduled for a transaction. |
receipt_retrieval.initiated | A receipt retrieval has started running for a transaction. |
receipt_retrieval.completed | A receipt was successfully attached to the transaction. |
receipt_retrieval.failed | The 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
| Field | Type | Description |
|---|---|---|
receipts | array | Every receipt on the transaction, newest first, including the one that triggered this event. Each item is a file object (below). |
receipts[].file_id | string | The 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[].url | string | A signed download URL, valid for 15 minutes from delivery. Retries carry freshly signed URLs. |
receipts[].size | integer | File size in bytes. |
receipts[].content_type | string | The file's MIME type, for example application/pdf. |
receipts[].created | integer | When the file was stored, in Unix seconds. |
receipt_file_url | string | The url of the receipt that triggered this event. Kept for integrations that predate receipts; new integrations should read receipts. |
booking_disposition | nullable enum | completed, 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.
| Code | Description |
|---|---|
booking_canceled | The reservation was canceled prior to check-in. |
no_response | The property did not respond to retrieval attempts. |
no_show | The customer did not show up for the booking. |
property_could_not_find | The property was unable to locate the booking. |
property_refused | The property declined to provide the receipt. |
skip_permanently | Versa stopped retrieval for this transaction; it was not worth pursuing (for example, a zero-amount charge). |
third_party_billing | The 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 theendpoint_urloreventsfor a specific webhook - PUT
/webhook/:id/rotate- Rotate thesecretfor a specific webhook - DELETE
/webhook/:id- Delete a specific webhook