# Receipt Webhooks

Receipt webhooks deliver encrypted, recipient-specific document bundles and their definitive CHEQI hash to authorized integrations.

## Subscribe

```json
{
  "name": "Receipt integration",
  "notificationUrl": "https://your-domain.example/webhooks/cheqi",
  "events": ["RECEIPT_CREATED"]
}
```

See [Webhook Setup](/webhooks/setup) for registration and [Webhook Security](/webhooks/security) for signature verification.

## Receipt-created flow

1. A merchant submits receipt-generation input encrypted independently for the matched owner devices.
2. Each owner device decrypts its job and generates its local CHEQI receipt with the shared Rust engine.
3. One leased device resolves current downstream recipients.
4. That device builds a `ReceiptEnvelope` for each recipient and encrypts it using that recipient's key.
5. Cheqi stores and routes the ciphertext without decrypting it.
6. Cheqi sends `RECEIPT_CREATED` to the subscribed receiving integration.


## `RECEIPT_CREATED`

```json
{
  "event": "RECEIPT_CREATED",
  "data": {
    "encryptedReceipt": {
      "clientId": "your-public-client-id",
      "companyId": "550e8400-e29b-41d4-a716-446655440000",
      "created_at": "2026-08-03T13:30:00Z",
      "cheqiReceiptId": "CHQ-20260803-ABC123",
      "encryptedEnvelope": "base64-ciphertext...",
      "encryptedEnvelopeKey": "base64-wrapped-aes-key...",
      "publicKey": "base64-recipient-public-key-snapshot...",
      "recipientKeyAlgorithm": "RSA_2048",
      "envelopeVersion": 1,
      "receiptGeneratorVersion": "0.3.0",
      "finalHash": "sha256-cheqi-document-hash..."
    }
  }
}
```

`finalHash` is the definitive CHEQI document hash submitted by the elected device together with the encrypted deliveries. A separate finalization event is not required.

The ciphertext and wrapped key are the values originally submitted by the elected owner device. Cheqi does not decrypt, rebuild, or merge the receipt documents.

### Decrypt into `ReceiptEnvelope`

After unwrapping the AES key and decrypting `encryptedEnvelope`, deserialize the plaintext JSON into `ReceiptEnvelope`:

```java
public record ReceiptEnvelope(
        int envelopeVersion,
        UUID receiptUuid,
        String cheqiReceiptId,
        String receiptGeneratorVersion,
        Map<String, ReceiptEnvelopeDocument> documents
) {}

public record ReceiptEnvelopeDocument(
        String mediaType,
        String content
) {}
```

Example processing logic:

```java
WebhookPayload receipt = event.data().encryptedReceipt();

byte[] plaintext = envelopeCrypto.decrypt(
        receipt.encryptedEnvelope(),
        receipt.encryptedEnvelopeKey(),
        receipt.publicKey(),
        receipt.recipientKeyAlgorithm()
);

ReceiptEnvelope envelope = objectMapper.readValue(
        plaintext,
        ReceiptEnvelope.class
);

ReceiptEnvelopeDocument cheqi = envelope.documents().get("CHEQI");
ReceiptEnvelopeDocument invoice = envelope.documents().get("UBL_INVOICE");
```

Use the SDK's envelope crypto implementation rather than inventing a separate wire format. The public-key snapshot lets an integration with rotated keys select the historical private key used for this delivery.

Documents determine formats
Inspect `ReceiptEnvelope.documents` to determine what was delivered. The webhook does not include `receiptFormats`, `encryptedCustomerDetails`, or `encryptedCustomerAesKey`.

## Handler outline

Verify the signature using the exact raw body before parsing it:

```java
public ResponseEntity<Void> receive(String rawBody, String signature) {
    if (!signatureVerifier.isValid(rawBody, signature)) {
        return ResponseEntity.status(401).build();
    }

    WebhookEvent event = objectMapper.readValue(rawBody, WebhookEvent.class);
    webhookQueue.enqueue(event);
    return ResponseEntity.ok().build();
}
```

In the asynchronous worker, route `RECEIPT_CREATED` to the envelope-decryption and reconciliation path. Store events idempotently because delivery can be retried.

For the complete event schemas, see [Webhook Events](/webhooks/events).