Skip to content

Customer return requests are encrypted for the original receipt issuer. Receive them in real time through a RETURN_REQUESTED webhook or retrieve outstanding requests from GET /credit-note.

Encryption key required

API-key integrations use the company's registered encryption key. OAuth integrations use the client application's registered key. Decrypt with the private key that matches the registered public key.

Webhook delivery

Subscribe to RETURN_REQUESTED. The event-specific payload is under data.creditNoteInitiationRequest:

{
  "event": "RETURN_REQUESTED",
  "data": {
    "creditNoteInitiationRequest": {
      "companyId": "550e8400-e29b-41d4-a716-446655440000",
      "userId": "8c669f72-65bb-4788-9ab1-5289452e05d1",
      "cheqiReceiptId": "CHQ-20260805-000001",
      "publicKey": "base64-public-key...",
      "encryptedCreditNoteInitiationRequest": "base64-ciphertext...",
      "encryptedSymmetricKey": "base64-wrapped-aes-key...",
      "clientId": null,
      "created_at": "2026-08-05T12:30:00Z"
    }
  }
}

companyId, userId, and clientId identify the applicable routing context and can be absent when they do not apply. Verify the webhook signature before processing the request, acknowledge promptly with a 2xx response, and make processing idempotent because delivery can be retried.

See Webhook Setup and Webhook Security.

Polling

Use the authenticated company or client-application credential:

curl https://api.cheqi.io/credit-note \
  -H "Authorization: Bearer <token>"

The endpoint returns an array of outstanding requests:

[
  {
    "requestId": "d79f71c6-ef10-4cd1-92ca-750bfdb31a0c",
    "cheqiReceiptId": "CHQ-20260805-000001",
    "encryptedSymmetricKey": "base64-wrapped-aes-key...",
    "publicKey": "base64-public-key...",
    "status": "PENDING",
    "createdAt": "2026-08-05T12:30:00Z",
    "encryptedCreditNoteInitiationRequest": "base64-ciphertext..."
  }
]
PropertyTypeDescription
requestIdUUIDQueue request identifier; use it for status updates.
cheqiReceiptIdstringCheqi identifier of the original receipt.
encryptedSymmetricKeystringAES content key wrapped for the issuer's registered RSA public key.
publicKeystringPublic key associated with the encrypted request.
statusenumPENDING, ACCEPTED, PARTIALLY_ACCEPTED, REJECTED, COMPLETED, or CANCELLED.
createdAtISO 8601 date-timeTime at which the request entered the queue.
encryptedCreditNoteInitiationRequeststringBase64-encoded encrypted plaintext request.

The polling response does not contain recipientId, returnRequestId, receiverType, encryptedCreditNote, customer-detail ciphertext fields, or supplierPartyId.

Decrypting with an SDK

Pass either the polling object or webhook.data.creditNoteInitiationRequest to the decryption service.

CreditNoteInitiationRequest request = sdk.getDecryptionService()
    .decryptCreditNoteInitiationRequest(encryptedRequest, privateKeyBase64);

String parentCheqiReceiptId = request.getCheqiReceiptId();
for (ReturnLineItem item : request.getLineItems()) {
    String productId = item.getProductId();
    BigDecimal quantity = item.getQuantity();
    ReturnReasonCode reason = item.getReasonCode();
    String explanation = item.getReasonDescription();
}
const request = sdk.decryptionService.decryptCreditNoteInitiationRequest(
  encryptedRequest,
  privateKeyBase64
);

const parentCheqiReceiptId = request.cheqiReceiptId;
for (const item of request.lineItems) {
  const { productId, quantity, reasonCode, reasonDescription } = item;
}

The SDK decrypts, deserializes, and validates the plaintext contract. If you decrypt manually, validate it against the model below before acting on it.

Decrypted request properties

{
  "cheqiReceiptId": "CHQ-20260805-000001",
  "receiptId": "INV-2026-0042",
  "customerNote": "The left shoe is damaged.",
  "lineItems": [
    {
      "productId": "SKU-AIRMAX-BLK-42",
      "quantity": 1,
      "reasonCode": "DAMAGED",
      "reasonDescription": "Damage near the heel"
    }
  ],
  "refundPreference": "ORIGINAL_PAYMENT_METHOD"
}
PropertyRules
cheqiReceiptIdRequired, non-empty.
receiptIdRequired, non-empty merchant receipt identifier.
customerNoteOptional; maximum 1,000 characters.
lineItemsRequired and non-empty.
refundPreferenceORIGINAL_PAYMENT_METHOD, BANK_TRANSFER, or STORE_CREDIT.
refundBankAccountRequired only for BANK_TRANSFER; forbidden for the other preferences.

Each line needs a non-empty productId of at most 255 characters, a positive quantity, and a valid reasonCode. reasonDescription is optional and limited to 1,000 characters. Multiple lines may use the same productId when different quantities have different reasons.

The productId is the identifier supplied on the original receipt product. Use it with receiptId or cheqiReceiptId to look up the authoritative original line, price, tax, and quantity in your own system.

Merchant validation

The return request describes what the customer wants; it does not authorize or calculate a refund. Before issuing a credit note:

  • Confirm that the receipt and product belong to the requesting customer.
  • Check the return window and your policy.
  • Confirm that the requested quantity does not exceed the remaining returnable quantity.
  • Calculate the accepted credit and tax from your authoritative transaction data.
  • Treat the refund preference as a request, not an instruction to move money automatically.
  • Validate bank details before using them.

Updating request status

After making a decision, update the queue item using its requestId:

curl -X PATCH https://api.cheqi.io/credit-note/requests/d79f71c6-ef10-4cd1-92ca-750bfdb31a0c/status \
  -H "Authorization: Bearer <token>" \
  -H "Content-Type: application/json" \
  -d '{"status":"ACCEPTED"}'

Use PARTIALLY_ACCEPTED when only part of the requested return is approved, REJECTED when none is approved, and COMPLETED after the accepted return and refund workflow is finished.

Next step

Build the definitive merchant payload and issue the linked document in Issuing Credit Notes.