Please make sure you read the Conventions before continuing with this guide.
Table of Contents
- Overview
- Prerequisites
- Supported Channels
- Complete Cancellation Flow
- Step 1: Getting Cancellable Items
- Step 2: Creating a Cancel Set
- Step 3: Executing the Cancellation
- Audit Fields: createdBy and updatedBy
- Error Handling
- Complete Examples
- Best Practices
- Additional Resources
- Support
Overview
This guide provides comprehensive documentation for performing cancellations using Betterez APIs by third parties. It covers the complete cancellation flow, channel-specific behaviors, how audit fields (createdBy and updatedBy) are set, error handling, and examples for all supported channels.
Prerequisites
You will need:
- An X-API-KEY
- A Basic Access Authentication token or JWT token
- For backoffice channels: A back-office user account with appropriate permissions
- For websales channels: A customer account or widget user configuration
Supported Channels
The cancellation API supports the following channels:
backoffice- For cancellations performed by back-office userswebsales- For cancellations performed by customers through web salesagency-backoffice- For cancellations performed by agency back-office usersagency-websales- For cancellations performed by agency customers through web sales
Complete Cancellation Flow
The cancellation process consists of three main steps:
- Get Cancellable Items - Retrieve items that can be cancelled from a transaction
- Create Cancel Set - Create a cancellation preview with calculated fees and refund amounts
- Execute Cancellation - Process the cancellation and refund payments
Step 1: Getting Cancellable Items
Endpoint
GET /operations/transactions/{transactionId}/cancellable-items
Description
This endpoint returns all items in a transaction that are eligible for cancellation, grouped by product family. Each item includes basic information needed to display cancellation options to users.
Request Parameters
| Parameter | Type | Location | Required | Description |
|---|---|---|---|---|
transactionId |
string | path | Yes | The transaction ID containing items to cancel |
displayAll |
boolean | query | No | If true, returns all items regardless of cancellability status |
channel |
string | query | Yes | The channel performing the cancellation (backoffice, websales, agency-backoffice, agency-websales) |
Channel-Specific Behavior
- backoffice/agency-backoffice: Returns cancellable items based on back-office rules and cutoff times (unless
allowReissueToAPastDateis enabled in account preferences - see Account Preferences in the back-office) - websales/agency-websales: Returns cancellable items based on customer-facing rules and cutoff times
Example Request
curl -X GET \
"https://api.betterez.com/operations/transactions/507f191e810c19729de860ee/cancellable-items?channel=backoffice" \
-H "X-API-KEY: your-api-key" \
-H "Authorization: Bearer your-jwt-token"
Response Structure
{
"tickets": [
{
"_id": "507f191e810c19729de860ea",
"accountId": "507f191e810c19729de86000",
"availableUses": 1,
"cancellable": true,
"channel": "backoffice",
"createdAt": {
"value": "2022-10-03T16:02:52.716Z",
"offset": 0
},
"displayTotal": 1130000,
"departureTime": "01:10",
"expirationDate": {
"value": "2022-10-11T00:00:00.000Z",
"offset": 0
},
"departureTimestamp": "2022-10-11T05:10:00.000Z",
"isExpirationDateCalculated": false,
"expire": 60,
"expireUnit": "days",
"fare": "Adult",
"firstName": "S",
"from": "Alban",
"lastName": "N",
"productFamily": "reservation",
"productType": "reservation",
"refId": "",
"status": "paid",
"ticketNumber": "A4NAG8",
"to": "Barrie",
"total": 1130000,
"trxId": "507f191e810c19729de860ee",
"expired": false
}
],
"flexPasses": [],
"reservations": [],
"parcels": [],
"fees": [],
"soldItems": [],
"insurances": [],
"redeemableItems": [],
"giftCertificates": []
}
Items Not Showing as Cancellable
Items may not appear as cancellable if:
- Item is already cancelled
- Item is expired (past departure date or cutoff times)
- Item is unpaid
- No available uses left (for flex passes)
- Item has been redeemed
- Gift certificate has been used at least once
- Parcel has been scanned
Note: If the displayAll query parameter is set to true, all items will be returned regardless of cancellability. However, attempting to cancel a non-cancellable item in the next step will result in an error.
Step 2: Creating a Cancel Set
Endpoint
POST /sales/cancellations
Description
This endpoint creates a cancellation preview (cancel set) that includes:
- Detailed item information
- Calculated cancellation fees
- Refund amounts per payment method
- Tax calculations
- Payment method options for refunds
The cancel set is read-only and must be sent unmodified to the next step. If modified, the signature validation will fail.
Request Body
{
"cancellation": {
"trxId": "507f191e810c19729de860ee",
"tickets": ["507f191e810c19729de860ea"],
"reservations": [],
"flexPasses": [],
"parcels": [],
"soldItems": [],
"redeemableItems": [],
"giftCertificates": [],
"insurances": [],
"fees": [],
"channel": "backoffice",
"penalty": {
"amount": 0,
"reason": ""
},
"overridedCancelFees": [],
"currency": "USD",
"terminalPayload": null
}
}
Request Parameters for Cancel Set
| Field | Type | Required | Description |
|---|---|---|---|
cancellation.trxId |
string | Yes | The transaction ID to cancel items from |
cancellation.tickets |
array | Yes | Array of ticket IDs to cancel (can be empty) |
cancellation.reservations |
array | Yes | Array of reservation IDs to cancel (can be empty) |
cancellation.flexPasses |
array | Yes | Array of flex pass IDs to cancel (can be empty) |
cancellation.parcels |
array | Yes | Array of parcel IDs to cancel (can be empty) |
cancellation.soldItems |
array | Yes | Array of sold item IDs to cancel (can be empty) |
cancellation.redeemableItems |
array | Yes | Array of redeemable item IDs to cancel (can be empty) |
cancellation.giftCertificates |
array | Yes | Array of gift certificate IDs to cancel (can be empty) |
cancellation.insurances |
array | Yes | Array of insurance IDs to cancel (can be empty) |
cancellation.fees |
array | Yes | Array of transaction fee indices to cancel (can be empty) |
cancellation.channel |
string | Yes | Channel identifier: backoffice, websales, agency-backoffice, or agency-websales |
cancellation.penalty |
object | Yes | Penalty amount and reason (optional, defaults to 0) |
cancellation.overridedCancelFees |
array | No | Array of cancel fees to override with reasons |
cancellation.currency |
string | No | Currency code for refund (defaults to transaction currency) |
cancellation.terminalPayload |
object | No | Terminal payment payload for in-person refunds |
Channel-Specific Behavior
Backoffice Channel (backoffice)
- User Authentication: Requires a back-office user JWT token
- Cutoff Times: Respects cutoff times unless
allowReissueToAPastDateis enabled in account preferences (configured in Account Preferences in the back-office) - Permissions: User must have appropriate cancellation permissions
- Fee Override: Allowed if user has
overrideCancelFeespermission - Penalty: Allowed if account has
enableArbitraryRefundsenabled and user has permission (configured in Account Preferences in the back-office) - Shift Requirement: May require an active shift depending on account configuration
Websales Channel (websales)
- User Authentication: Uses customer JWT token or widget user
- Cutoff Times: Always respects cutoff times
- Fee Override: Not allowed - customer cannot override fees
- Penalty: Not allowed - customer cannot add penalties
- Shift Requirement: No shift required
- User Resolution: The
userIdis extracted from the authenticated JWT token. If the JWT token does not contain user information, the system automatically uses the widget user configured for the account
Agency-Backoffice Channel (agency-backoffice)
- User Authentication: Requires agency back-office user JWT token
- Agency Validation: Account must be a valid agency for the transaction
- Cutoff Times: Respects cutoff times unless
allowReissueToAPastDateis enabled (configured in Account Preferences in the back-office) - Permissions: User must have appropriate cancellation permissions
- Fee Override: Allowed if user has
overrideCancelFeespermission - Penalty: Allowed if account has
enableArbitraryRefundsenabled (configured in Account Preferences in the back-office)
Agency-Websales Channel (agency-websales)
- User Authentication: Uses agency customer JWT token or widget user
- Agency Validation: Account must be a valid agency for the transaction
- Cutoff Times: Always respects cutoff times
- Fee Override: Not allowed
- Penalty: Not allowed
- Shift Requirement: No shift required
Optional: Adding a Penalty
To add a penalty amount to the cancellation:
{
"cancellation": {
"trxId": "507f191e810c19729de860ee",
"tickets": ["507f191e810c19729de860ea"],
"channel": "backoffice",
"penalty": {
"amount": 100000,
"reason": "Late cancellation request"
}
}
}
Requirements:
- Channel must be
backofficeoragency-backoffice - Account must have
enableArbitraryRefundsenabled (configured in Account Preferences in the back-office) - User must have permission to apply penalties
- Penalty amount cannot exceed refundable amount
Optional: Overriding Cancel Fees
To prevent specific cancel fees from applying:
{
"cancellation": {
"trxId": "507f191e810c19729de860ee",
"tickets": ["507f191e810c19729de860ea"],
"channel": "backoffice",
"overridedCancelFees": [
{
"internalId": "cancel-fee-001",
"reason": "Customer service exception"
}
]
}
}
Requirements:
- Channel must be
backofficeoragency-backoffice - User must have
overrideCancelFeespermission - Each override must include a reason
Response Structure
{
"_id": "507f191e810c19729de860ea",
"cancellation": {
"trxId": "507f191e810c19729de860ee",
"transactionId": "ABCDFG12",
"customerNumber": "123-456-789",
"tickets": [
{
"_id": "507f191e810c19729de860ea",
"trxId": "507f191e810c19729de860ee",
"accountId": "507f191e810c19729de86000",
"ticketNumber": "ABCDEF",
"ticketId": "507f191e810c19729de860ea",
"productFamily": "ticket",
"productType": "ticket",
"productId": "507f191e810c19729de860ea",
"totalRefundable": 6150000,
"displayTotalRefundable": 6150000,
"rounding": {
"policy": "default",
"decimals": "2"
},
"fareClassId": null,
"fees": [],
"taxes": [],
"status": "paid",
"ratios": [
{
"type": "cash",
"total": 6260000,
"displayTotal": 6260000,
"extraInfo": null
}
],
"cancelFees": [
{
"calculated": 615000,
"_id": "507f191e810c19729de860ea",
"userId": "507f191e810c19729de860ea",
"accountId": "507f191e810c19729de860ea",
"internalId": "Example cancel fee",
"name": "Example cancel fee",
"valueType": "%",
"value": 10000,
"taxable": true,
"isRefundable": true,
"valueToDisplay": "10",
"displayCalculated": 615000,
"taxes": [
{
"name": "ontax",
"value": 5000,
"province": "Ontario",
"calculated": 28000,
"displayCalculated": 28000
}
],
"taxesTotal": 56000,
"displayTaxesTotal": 56000
}
],
"expirationDate": {},
"expiryDate": null,
"price": 4590000,
"total": 6260000,
"displayPrice": 4590000,
"displayTotal": 6260000,
"subTotal": 0,
"totalTaxes": 560000,
"displaySubTotal": 0,
"displayTotalTaxes": 560000,
"flexPassOptions": {},
"currentScansPerDay": {},
"availableUses": 1,
"totalCancelFees": 615000,
"displayTotalCancelFees": 615000,
"createdFromFlexpassId": null,
"payment": [
{
"provider": "inperson",
"type": "cash",
"ticketNumber": "ABCDEF",
"amount": 5535000,
"acceptedCurrencyAmount": 5535000,
"percentage": 100
}
],
"refunded": 5535000,
"displayRefunded": 5535000,
"itemType": "reservation",
"totalRefundablePreTax": 5590000,
"displayTotalRefundablePreTax": 5590000
}
],
"reservations": [],
"soldItems": [],
"flexPasses": [],
"redeemableItems": [],
"giftCertificates": [],
"parcels": [],
"insurances": [],
"trxFees": [],
"cancelFees": [
{
"internalId": "Example cancel fee",
"name": "Example cancel fee",
"calculated": 616000,
"displayCalculated": 616000,
"taxesTotal": 56000,
"value": 10000,
"valueType": "%"
}
],
"overridedCancelFees": [],
"displayCurrency": {
"isocode": "USD",
"symbol": "$",
"buy": 1,
"sell": 1,
"channels": ["backoffice"]
},
"penalty": {
"amount": 0,
"acceptedAmount": 0,
"reason": ""
},
"payments": [
{
"_id": "a1b2c3d4-abdc-abcd-1234-a1b2c3d4f555",
"method": "cash",
"provider": "inperson",
"displayName": "cash",
"firstName": "FirstName",
"lastName": "LastName",
"currency": "USD",
"card": "",
"authorization": "",
"amount": "55.44",
"acceptedCurrencyAmount": "55.44",
"feesAndPenaltyAmount": "6.16",
"displayFeesAndPenaltyAmount": "6.16",
"feesAndPenaltyAmountValue": 616000,
"displayFeesAndPenaltyAmountValue": 616000,
"amountValue": 5544000,
"acceptedCurrencyAmountValue": 5544000
}
],
"channel": "backoffice"
},
"signature": "86734655de9f997daddb61927f1a4111918d777493d748300689c0b0a55a87c899cad21b0b08c80a3c534b687cedf5ff",
"dupKey": "5cfd287b1ebb1983fd47ca61cc1e1e96cab3cd0e519e8a8b9021692a58b817a1e5c78dd892d41ad59b92637bc4528f8f"
}
Important:
- The
signaturefield is used to verify the cancel set has not been modified. Do not modify any fields in the response before sending it to the next step. - The
dupKeyfield is used for idempotency to prevent duplicate cancellations. It is automatically generated by the system and must be included in the PUT request. If the samedupKeyis submitted within 30 seconds, the request will be rejected with a 409 Conflict error indicating the cancellation is already in progress.
Step 3: Executing the Cancellation
Endpoint
PUT /sales/cancellations
Description
This endpoint processes the cancellation and executes refunds through payment providers. It:
- Validates the cancel set signature
- Updates item statuses to "cancelled" or "refunding"
- Processes refund payments through payment providers
- Creates a new refund transaction
- Updates the original transaction
- Emits webhook events
Request Body
Send the complete, unmodified cancel set response from Step 2:
{
"cancelSet": {
"_id": "507f191e810c19729de860ea",
"cancellation": {
// ... complete cancel set from Step 2
},
"signature": "86734655de9f997daddb61927f1a4111918d777493d748300689c0b0a55a87c899cad21b0b08c80a3c534b687cedf5ff",
"dupKey": "5cfd287b1ebb1983fd47ca61cc1e1e96cab3cd0e519e8a8b9021692a58b817a1e5c78dd892d41ad59b92637bc4528f8f"
}
}
Note: The dupKey field is automatically included in the cancel set response from Step 2. It must be included in the PUT request to enable idempotency protection against duplicate submissions.
Channel-Specific Behavior for Execution
Backoffice Channel Execution (backoffice)
- User Resolution: Uses the user information from the authenticated JWT token. The
userIdis automatically extracted from the JWT token when a back-office user authenticates - Shift Requirement: May require an active shift if user has shift permissions
- Payment Processing: Supports all payment methods available for the account
- Terminal Payload: Can include terminal payload for in-person refunds
Websales Channel Execution (websales)
- User Resolution:
- If the JWT token contains user information, uses that user's ID as the
userId - Otherwise, if no user information is present in the JWT token, uses the widget user configured for the account
- If the JWT token contains user information, uses that user's ID as the
- Customer Validation: Validates that the customer number in the JWT token matches the transaction customer
- Shift Requirement: No shift required
- Payment Processing: Limited to online payment methods typically
- Terminal Payload: Not applicable
Agency-Backoffice Channel Execution (agency-backoffice)
- User Resolution: Uses the agency user information from the authenticated JWT token. The
userIdis automatically extracted from the JWT token when an agency back-office user authenticates - Agency Validation: Validates account is a valid agency for the transaction
- Shift Requirement: May require an active shift
- Payment Processing: Supports payment methods available to the agency
- Interline Handling: May create consumer transaction for interline accounting
Agency-Websales Channel Execution (agency-websales)
- User Resolution:
- If the JWT token contains user information, uses that user
- Otherwise, uses the widget user configured for the agency account
- Customer Validation: Validates customer matches transaction
- Agency Validation: Validates account is a valid agency
- Shift Requirement: No shift required
- Payment Processing: Limited to online payment methods
Response Structure
{
"transaction": {
"_id": "507f191e810c19729de860eb",
"transactionId": "REFUND01",
"status": "cancelled",
"accountId": "507f191e810c19729de86000",
"associatedTransactionId": "ABCDFG12",
"associatedTrxId": "507f191e810c19729de860ee",
"customerNumber": "123-456-789",
"total": -5535000,
"displayTotal": -55.35,
"summary": [
{
"_id": "507f191e810c19729de860ea",
"status": "cancelled",
"refunded": true,
"refId": "507f191e810c19729de860ec",
"refundId": "R-ABCDEF",
"total": -5535000,
"displayTotal": -55.35
}
],
"payments": [
{
"method": "cash",
"provider": "inperson",
"amount": -55.44,
"status": "success"
}
],
"createdBy": "507f191e810c19729de860ed",
"createdByUserEmail": "user@example.com",
"createdAt": {
"value": "2022-10-03T16:05:00.000Z",
"offset": 0
},
"originalPurchaseChannel": "backoffice"
}
}
Important Notes
Single Online Payment Limitation: At the moment, it is only possible to refund a single online payment at a time. If there are multiple online payment methods in the same transaction and no override payment was provided, the cancellation will not be performed and will return a MORE_THAN_ONE_ONLINE_CREDIT_CARD error. To handle transactions with multiple online payments, you must provide an override payment method in the cancellation request.
Audit Fields: createdBy and updatedBy
The system sets audit fields (createdBy, updatedBy, createdByUserEmail, updatedByUserEmail) differently based on the channel and context.
Transaction Object
createdBy / createdByUserEmail
Set when the refund transaction is created:
Backoffice/Agency-Backoffice:
createdBy: Set to the user ID from the JWT tokencreatedByUserEmail: Set to the user email from the JWT token- If the JWT token does not contain user information, these fields are
null
Websales/Agency-Websales:
createdBy: Set to the widget user ID if the JWT token does not contain user information, otherwise set to the user ID from the JWT tokencreatedByUserEmail: Set to the widget user email if the JWT token does not contain user information, otherwise set to the user email from the JWT token- The widget user is automatically selected from the account configuration
updatedBy / updatedByUserEmail
- Set when the original transaction is updated during cancellation
- Always set to the user performing the cancellation (same logic as
createdBy)
Refund Object
createdBy / createdByUserEmail
- Set to the same user as the transaction
createdBy - Determined from the user information in the JWT token used for the cancellation request
updatedBy / updatedByUserEmail
- Initially set to
createdByvalues - Updated when refund status changes
Cancelled Items (Tickets, Parcels, etc.)
updatedBy / updatedByUserEmail
- Set when item status is updated to "cancelled" or "refunding"
- Always set to the user email from the JWT token used in the cancellation request
Original Transaction
updatedBy / updatedByUserEmail
- Set when transaction summary items are updated with refund information
- Always set to the user email from the JWT token used in the cancellation request
Summary Table
| Object | Field | Backoffice | Websales | Agency-Backoffice | Agency-Websales |
|---|---|---|---|---|---|
| Refund Transaction | createdBy |
User ID from JWT token | Widget user ID or user ID from JWT token | User ID from JWT token | Widget user ID or user ID from JWT token |
| Refund Transaction | createdByUserEmail |
User email from JWT token | Widget user email or user email from JWT token | User email from JWT token | Widget user email or user email from JWT token |
| Refund Object | createdBy |
User ID from JWT token | Widget user ID or user ID from JWT token | User ID from JWT token | Widget user ID or user ID from JWT token |
| Refund Object | createdByUserEmail |
User email from JWT token | Widget user email or user email from JWT token | User email from JWT token | Widget user email or user email from JWT token |
| Cancelled Items | updatedByUserEmail |
User email from JWT token | Widget user email or user email from JWT token | User email from JWT token | Widget user email or user email from JWT token |
| Original Transaction | updatedByUserEmail |
User email from JWT token | Widget user email or user email from JWT token | User email from JWT token | Widget user email or user email from JWT token |
Error Handling
Common Errors
400 Bad Request
| Error Code | Description | Solution |
|---|---|---|
WRONG_DATA |
Missing required cancellation data | Ensure all required fields are provided |
INVALID_ITEM_ID |
Invalid item ID provided | Verify item IDs match those from cancellable items endpoint |
INVALID_DOCUMENT |
Invalid document structure | Check request body structure matches API specification |
MISSING_CANCELLABLE_IDS |
Missing or invalid refundable model IDs | Ensure all item IDs are valid and belong to the transaction |
NO_REFUNDABLE_METHODS |
No refundable payment methods available | Check payment methods configuration |
MORE_THAN_ONE_ONLINE_CREDIT_CARD |
Multiple online credit card payments found | Only one online credit card refund allowed at a time |
REMAINING_FEE_OR_PENALTY |
Outstanding balance after fees/penalty | Adjust penalty or fee override amounts |
NO_REFUNDABLES_FOUND |
No items found for given IDs | Verify item IDs are correct |
TRX_FEE_MISSING_REFUNDABLES |
Transaction fee refund requires all items | Select all items when refunding transaction fees |
INVALID_FEE_OVERRIDE |
Fee override sent but no cancel fee found | Verify cancel fee internalId exists |
INVALID_FEE_OVERRIDE_REASON |
Fee override missing reason | Provide reason for each fee override |
NO_PENALTY_REASON |
Penalty amount set without reason | Provide reason when setting penalty |
PENALTY_NOT_ALLOWED |
Arbitrary refunds disabled | Enable enableArbitraryRefunds in account preferences (configured in Account Preferences in the back-office) |
DIFFERENT_FARE_CLASSES |
Tickets have different fare classes | Only cancel tickets with same fare class together |
INVALID_PENALTY |
Penalty exceeds refundable amount or is negative | Adjust penalty amount |
INVALID_CANCEL_SET |
Missing fields in cancel set | Ensure cancel set is complete |
INVALID_SIGNATURE |
Cancel set signature mismatch | Do not modify cancel set between POST and PUT |
[itemType]_NOT_FOUND |
Item not found | Verify item ID and transaction ID |
[itemType]_NOT_REFUNDABLE |
Item is not refundable | Check item status and cutoff times |
SHIFT_REQUIRED_TO_REFUND |
Active shift required | Open a shift for the user |
SHIFT_STATION_NOT_FOUND |
Shift station not found | Verify user shift configuration |
REQUIRED_COMPANION_ERROR |
Companion tickets must be cancelled together | Include all companion tickets in cancellation |
INVALID_TERMINAL_PAYLOAD |
Invalid terminal payload | Verify terminal payload structure |
UNUSED_TERMINAL_PAYLOAD |
Terminal payload provided but not used | Remove unused terminal payload |
MISSING_TERMINAL_PAYLOAD |
Terminal payload required but missing | Provide terminal payload for in-person refunds |
MISSING_REFERENCED_TRX |
Missing referenced transaction | Verify transaction association |
INVALID_AUTH_CODE |
Missing authorization code | Provide authorization code for refund payment |
CURRENCY_EXCHANGE_MISMATCH |
Currency mismatch with station | Use correct currency for refund |
AGENCY_CURRENCY_MISMATCH |
Currency mismatch with provider | Use correct currency for agency refunds |
401 Unauthorized
| Error Code | Description | Solution |
|---|---|---|
UNAUTHORIZED |
Missing or invalid X-API-KEY or Authorization header | Verify API key and token are correct |
403 Forbidden
| Error Code | Description | Solution |
|---|---|---|
NOT_ALLOWED_FOR_ONLINE |
Insufficient permissions for online refunds | User needs online refund permissions |
FEE_OVERRIDE_NOT_ALLOWED |
User cannot override cancel fees | User needs fee override permissions |
INVALID_AGENCY |
Account is not a valid agency | Verify agency relationship |
404 Not Found
| Error Code | Description | Solution |
|---|---|---|
TRANSACTION_NOT_FOUND |
Transaction not found | Verify transaction ID |
CUSTOMER_NOT_FOUND |
Customer not found | Verify customer number |
409 Conflict
| Error Code | Description | Solution |
|---|---|---|
CONFLICT |
Cancellation already in progress | This error occurs when the same dupKey is submitted within 30 seconds, indicating a duplicate cancellation attempt. Wait for the current cancellation to complete or ensure you're not resubmitting the same cancel set |
500 Internal Server Error
| Error Code | Description | Solution |
|---|---|---|
MISSING_UPDATE_MODEL |
Unimplemented update model | Contact support |
PAYMENT_REFUND_NOT_IMPLEMENTED |
Payment method refund not implemented | Contact support |
PAYMENT_ROLLBACK_FAILED |
Refund rollback failed | Contact support |
REFUND_EXECUTION_ERROR |
Payment provider error during refund | Check payment provider status |
[PROVIDER]_ERROR |
Specific payment provider error | Check provider-specific error details |
Error Response Format
{
"code": "INVALID_ITEM_ID",
"message": "Invalid item ID",
"detail": "Additional error details if available"
}
Complete Examples
Example 1: Backoffice Cancellation
# Step 1: Get cancellable items
curl -X GET \
"https://api.betterez.com/operations/transactions/507f191e810c19729de860ee/cancellable-items?channel=backoffice" \
-H "X-API-KEY: your-api-key" \
-H "Authorization: Bearer backoffice-user-jwt-token"
# Step 2: Create cancel set
curl -X POST \
"https://api.betterez.com/sales/cancellations" \
-H "X-API-KEY: your-api-key" \
-H "Authorization: Bearer backoffice-user-jwt-token" \
-H "Content-Type: application/json" \
-d '{
"cancellation": {
"trxId": "507f191e810c19729de860ee",
"tickets": ["507f191e810c19729de860ea"],
"reservations": [],
"flexPasses": [],
"parcels": [],
"soldItems": [],
"redeemableItems": [],
"giftCertificates": [],
"insurances": [],
"fees": [],
"channel": "backoffice",
"penalty": {
"amount": 0,
"reason": ""
}
}
}'
# Step 3: Execute cancellation (use complete response from Step 2)
curl -X PUT \
"https://api.betterez.com/sales/cancellations" \
-H "X-API-KEY: your-api-key" \
-H "Authorization: Bearer backoffice-user-jwt-token" \
-H "Content-Type: application/json" \
-d '{
"cancelSet": {
// ... complete cancel set from Step 2 response
}
}'
Example 2: Websales Cancellation
# Step 1: Get cancellable items
curl -X GET \
"https://api.betterez.com/operations/transactions/507f191e810c19729de860ee/cancellable-items?channel=websales" \
-H "X-API-KEY: your-api-key" \
-H "Authorization: Bearer customer-jwt-token"
# Step 2: Create cancel set
curl -X POST \
"https://api.betterez.com/sales/cancellations" \
-H "X-API-KEY: your-api-key" \
-H "Authorization: Bearer customer-jwt-token" \
-H "Content-Type: application/json" \
-d '{
"cancellation": {
"trxId": "507f191e810c19729de860ee",
"tickets": ["507f191e810c19729de860ea"],
"reservations": [],
"flexPasses": [],
"parcels": [],
"soldItems": [],
"redeemableItems": [],
"giftCertificates": [],
"insurances": [],
"fees": [],
"channel": "websales",
"penalty": {
"amount": 0,
"reason": ""
}
}
}'
# Step 3: Execute cancellation
curl -X PUT \
"https://api.betterez.com/sales/cancellations" \
-H "X-API-KEY: your-api-key" \
-H "Authorization: Bearer customer-jwt-token" \
-H "Content-Type: application/json" \
-d '{
"cancelSet": {
// ... complete cancel set from Step 2 response
}
}'
Example 3: Agency-Backoffice Cancellation with Fee Override
# Step 2: Create cancel set with fee override
curl -X POST \
"https://api.betterez.com/sales/cancellations" \
-H "X-API-KEY: agency-api-key" \
-H "Authorization: Bearer agency-user-jwt-token" \
-H "Content-Type: application/json" \
-d '{
"cancellation": {
"trxId": "507f191e810c19729de860ee",
"tickets": ["507f191e810c19729de860ea"],
"reservations": [],
"flexPasses": [],
"parcels": [],
"soldItems": [],
"redeemableItems": [],
"giftCertificates": [],
"insurances": [],
"fees": [],
"channel": "agency-backoffice",
"penalty": {
"amount": 0,
"reason": ""
},
"overridedCancelFees": [
{
"internalId": "cancel-fee-001",
"reason": "Customer service exception - weather delay"
}
]
}
}'
Best Practices
Always use the channel parameter: Specify the correct channel in all requests to ensure proper validation and behavior
Handle errors gracefully: Implement retry logic for transient errors (500, 409) but not for validation errors (400)
Validate cancel set signature: Never modify the cancel set between POST and PUT requests
Check item cancellability: Use the cancellable items endpoint before attempting cancellation
Monitor webhook events: Subscribe to transaction and refund webhooks to track cancellation status
Currency handling: Ensure refund currency matches the original transaction currency or is supported by the account
Idempotency: The
dupKeyfield is automatically included in the cancel set response and must be sent in the PUT request. This prevents duplicate cancellations - if the samedupKeyis submitted within 30 seconds, the request will be rejected with a 409 Conflict error
Additional Resources
- Making a Ticket Cancellation - Basic cancellation guide
- Conventions - API conventions and standards
- Authentication - Authentication guide
- Payment Payload Details - Terminal payment payload structure
API Documentation
For detailed API specifications, refer to the interactive API documentation:
Postman Resources
If you would like to use Postman to test the cancellation API, we have included a collection you can use as a starting point.
This collection is designed to allow you to perform a cancellation by following the steps above. To use it successfully, you will need:
- Your X-API-KEY
- JWT Token
- A cancellable transaction ID
- Account data (like stations, purchases, payment types, etc.) set up for your account
After importing the collection and the environment, make sure your collection is using the new environment, and that you have set the initial values.
On each step, each pre-script and test script will populate environment variables needed for the next step. You will only need to set the transactionId manually.
If you are going to be using our API in sandbox, please change the {{basePath}} environment variable to https://sandbox-api.betterez.com.
Support
For additional support or questions about cancellations:
- Review the API documentation in Swagger/ReDoc
- Contact Betterez support at support@betterez.com
- Check account-specific configuration in the back-office