> ## Documentation Index
> Fetch the complete documentation index at: https://docs.opencharge.network/llms.txt
> Use this file to discover all available pages before exploring further.

# Transfer Webhook

> Receive signed transfer proofs when payments to your merchant are completed

<Info>
  This is the primary way you learn that a payment has been completed. When a transfer involving your OCID finishes, the payment gateway POSTs a signed proof to this endpoint.
</Info>

## Flow

1. Customer pays via a payment gateway
2. Gateway completes the transfer to your OCID
3. Gateway POSTs signed proof to your `/transfer/webhook`
4. You verify the proof and mark the order as paid

## Verifying the Proof

Always verify the proof before trusting it:

```javascript theme={null}
app.post('/transfer/webhook', async (req, res) => {
  const { proof, signature } = req.body;

  // 1. Check the issuer is trusted
  if (!acceptedIssuers.includes(proof.issuer)) {
    return res.status(400).json({
      error: { code: 'ISSUER_NOT_ACCEPTED', message: 'Unknown issuer' }
    });
  }

  // 2. Fetch issuer's public key
  const issuerMetadata = await fetch(`${issuerEndpoint}/metadata.json`);
  const publicKey = issuerMetadata.config.publicKey;

  // 3. Verify signature
  const canonical = JSON.stringify(proof, Object.keys(proof).sort());
  if (!secp256k1_verify(publicKey, signature, SHA256(canonical))) {
    return res.status(400).json({
      error: { code: 'PROOF_SIGNATURE_INVALID', message: 'Invalid signature' }
    });
  }

  // 4. Check recipient is your OCID
  if (proof.to.ocid !== YOUR_OCID) {
    return res.status(400).json({
      error: { code: 'INVALID_PROOF', message: 'Wrong recipient' }
    });
  }

  // 5. Find and update the order
  const order = db.getOrderByReference(proof.to.reference);
  if (order && proof.amount === order.amount) {
    db.markOrderPaid(order.id, proof.txid, proof.timestamp);
  }

  res.json({ status: 'accepted', txid: proof.txid });
});
```

## Proof Fields

| Field                | Description                                    |
| -------------------- | ---------------------------------------------- |
| `proof.txid`         | Unique transaction ID from the issuer          |
| `proof.issuer`       | OCID of the service that executed the transfer |
| `proof.from.ocid`    | Sender's OCID                                  |
| `proof.to.ocid`      | Your merchant OCID                             |
| `proof.to.reference` | Your order ID (if provided during payment)     |
| `proof.amount`       | Transfer amount                                |
| `proof.currency`     | Currency code                                  |
| `proof.timestamp`    | When the transfer completed                    |

## Respond to the webhook call

Return `accepted` if you processed the notification:

```json theme={null}
{
  "status": "accepted",
  "txid": "gateway_tx_456"
}
```

Return `rejected` with a reason if there's an issue:

```json theme={null}
{
  "status": "rejected",
  "txid": "gateway_tx_456",
  "message": "Order already paid"
}
```


## OpenAPI

````yaml merchant-api/endpoint/merchant-api/openapi.json post /transfer/webhook
openapi: 3.1.0
info:
  title: Opencharge Merchant API
  description: >-
    API specification for merchants integrating with the Opencharge protocol.
    These endpoints are implemented by merchants and called by payment apps and
    gateways.
  version: 0.1.0
  license:
    name: MIT
servers:
  - url: https://api.merchant.example/opencharge
    description: Example merchant Opencharge endpoint
security: []
paths:
  /transfer/webhook:
    post:
      summary: Instant Transfer Notification (ITN)
      description: >-
        Receive signed transfer proofs on this endpoint. When a transfer
        involving your OCID is completed, a signed proof is POSTed here. Verify
        the proof signature, validate the issuer is trusted, and take
        appropriate action (e.g., mark order as paid, fulfill the order).
      operationId: transferWebhook
      parameters:
        - $ref: '#/components/parameters/X-OC-ID'
        - $ref: '#/components/parameters/X-OC-Timestamp'
        - $ref: '#/components/parameters/X-OC-Nonce'
        - $ref: '#/components/parameters/X-OC-Signature'
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/TransferWebhookRequest'
            example:
              proof:
                txid: gateway_tx_456
                issuer: 100
                from:
                  ocid: 200
                  reference: wallet_tx_123
                to:
                  ocid: 500
                  reference: ord_abc123
                amount: '15.00'
                currency: USD
                timestamp: 1706500500
                memo: Payment for ord_abc123
              signature: a1b2c3d4e5f6...
      responses:
        '200':
          description: Notification received and processed
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/TransferWebhookResponse'
              example:
                status: accepted
                txid: gateway_tx_456
        '400':
          description: Invalid proof or signature
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
        '401':
          description: Authentication failed
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
components:
  parameters:
    X-OC-ID:
      name: X-OC-ID
      in: header
      required: true
      description: Sender's OCID (Opencharge ID)
      schema:
        type: string
      example: '200'
    X-OC-Timestamp:
      name: X-OC-Timestamp
      in: header
      required: true
      description: Unix timestamp in seconds
      schema:
        type: string
      example: '1706500000'
    X-OC-Nonce:
      name: X-OC-Nonce
      in: header
      required: true
      description: Unique request identifier for replay protection
      schema:
        type: string
      example: req_abc123
    X-OC-Signature:
      name: X-OC-Signature
      in: header
      required: true
      description: secp256k1 ECDSA signature of the canonical request
      schema:
        type: string
      example: a1b2c3d4...7f1b
  schemas:
    TransferWebhookRequest:
      type: object
      required:
        - proof
        - signature
      properties:
        proof:
          $ref: '#/components/schemas/TransactionProof'
        signature:
          type: string
          description: secp256k1 signature of the proof from the issuer
    TransferWebhookResponse:
      type: object
      required:
        - status
        - txid
      properties:
        status:
          type: string
          enum:
            - accepted
            - rejected
          description: Whether you accepted the notification
        txid:
          type: string
          description: The transaction ID from the proof
        message:
          type: string
          description: Optional message explaining rejection reason
    Error:
      type: object
      required:
        - error
      properties:
        error:
          type: object
          required:
            - code
            - message
          properties:
            code:
              type: string
              description: Error code
              enum:
                - INVALID_SIGNATURE
                - TIMESTAMP_EXPIRED
                - NONCE_REUSED
                - UNKNOWN_OCID
                - INVALID_PROOF
                - PROOF_SIGNATURE_INVALID
                - ISSUER_NOT_ACCEPTED
                - ORDER_NOT_FOUND
                - ORDER_EXPIRED
            message:
              type: string
              description: Human-readable error message
            details:
              type: object
              description: Additional error context
    TransactionProof:
      type: object
      required:
        - txid
        - issuer
        - from
        - to
        - amount
        - currency
        - timestamp
      properties:
        txid:
          type: string
          description: Unique transaction ID from issuer
        issuer:
          type: integer
          description: OCID of service that executed the transfer
        from:
          type: object
          required:
            - ocid
          description: Sender information
          properties:
            ocid:
              type: integer
              description: Sender's OCID
            reference:
              type: string
              description: Sender's transaction reference
        to:
          type: object
          required:
            - ocid
          description: Recipient information
          properties:
            ocid:
              type: integer
              description: Recipient's OCID (your merchant OCID)
            reference:
              type: string
              description: Your order ID or transaction reference
        amount:
          type: string
          description: Amount as decimal string
        currency:
          type: string
          description: ISO 4217 currency code
        timestamp:
          type: integer
          description: Unix timestamp when transfer completed
        memo:
          type: string
          description: Human-readable description

````