Skip to documentationDocumentation
On this page

Create

Create a refund for a closed invoice with accepted funds.

Creates a merchant-funded refund for a paid, overpaid, underpaid, expired, or invalid invoice with accepted funds to a supplied destination in the invoice's currency and network. The requested amount is capped by accepted gross payments minus completed and in-flight refund principal. The merchant debit and recipient amount follow the snapshotted refund and configured network fee allocations. Actual on-chain network cost is reported separately and is not added to the merchant debit. The refund has its own on-chain execution; no separate payout is created.

Usage notes#

Requires refunds:write. A closed invoice with accepted funds is refundable (paid, overpaid, underpaid, expired, or invalid). Use the invoice's currency/network pair and a recipient-confirmed return address. Partial refunds are allowed within the accepted-gross remainder after completed and in-flight refund principals.

Save the refund ID. Its payout_id equals that ID and does not refer to a separate payout resource. recipient_amount and merchant_debit_amount already reflect commercial fee allocation; do not add measured network cost again.

If this request is refused, a corrected body may reuse the key only when Idempotency-Key-Released: true confirms release. See refunds and idempotency.

An invoice with a confirmed full refund request cannot accept a separate custom-address refund through this operation. A reversed refund or an uncertain earlier execution also blocks a replacement until reconciled. Use the invoice refund history in the dashboard for manual full refunds.

POST/v1/refunds
curl
curl --request POST 'https://api.thehalfin.com/v1/refunds' \
  --header "X-API-Key: $HALFIN_API_KEY" \
  --header 'Idempotency-Key: createRefund-example-1001' \
  --header 'Content-Type: application/json' \
  --data '{
  "invoice_id": "00000000-0000-0000-0000-000000000003",
  "currency": "USDC",
  "network": "ethereum",
  "amount": "5.00",
  "destination": "0x0000000000000000000000000000000000000001",
  "description": "Partial refund for order 1001"
}'

Authorization

  • X-API-KeystringRequired
    API key issued by the merchant dashboard. The key's environment (live/test) determines which data the caller can access. Permissions on the key control which endpoints are reachable.

    Example: $HALFIN_API_KEY

Header Parameters

  • Idempotency-KeystringRequired

    Persist a unique key before sending this operation. Retry an uncertain outcome with the same key and unchanged request; a new operation needs a new key.

    See idempotency and retries for replay, conflict, and key-release behavior.

    Full header contract

    Required for merchant POST requests that create invoices, addresses, payouts, or refunds. Use a unique key for each intended operation. The same key namespace is shared by all merchant operations within the same merchant and environment. Retry the original request with the same key and unchanged path, query, and body. JSON key ordering and insignificant whitespace are ignored; numeric literals and string values are preserved ("10" differs from "10.00"). Changing any field under a bound key returns 409 conflict. An executing request returns 409 in_flight. An exact retry returns the permanent original outcome with Idempotent-Replayed: true. Only Idempotency-Key-Released: true confirms that a refused attempt released the key. An unconfirmed release returns 503 service_unavailable without that header. Under uncertainty, retry the original request with the same key. The key is never echoed in response bodies. Keys contain 1–255 printable ASCII characters without spaces; a UUID is recommended.

    • Minimum length: 1
    • Maximum length: 255
    • Pattern: ^[\x21-\x7E]+$

    Example: createRefund-example-1001

Request Body

Required

application/json

  • invoice_idstringRequired
    Invoice UUID whose accepted payments bound the refund amount.
    • Format: uuid

    Example: 00000000-0000-0000-0000-000000000001

  • currencystringRequired
    Credited asset currency to refund.
    • Minimum length: 1
    • Maximum length: 20

    Example: string

  • networkstringRequired
    Credited asset network. Required together with currency; an unknown pair returns 422 gate_unknown.
    • Minimum length: 1
    • Maximum length: 64

    Example: string

  • amountstringRequired
    Decimal string amount to refund.
    • Maximum length: 50

    Example: 1

  • destinationstringRequired
    Customer-supplied destination address.
    • Maximum length: 256

    Example: string

  • destination_tagstringOptional
    Memo/tag for chains that require it.
    • Maximum length: 256

    Example: string

  • descriptionstringOptional
    Human-readable refund description.
    • Maximum length: 1000

    Example: string

  • metadataobjectOptional
    Merchant key-value metadata; omitted invoice metadata is stored and returned as an empty object.
    • Maximum properties: 50

    Example: {"key":"string"}

    • [key]stringOptional
      • Maximum length: 500

      Example: string

Response Body

201 Refund created.

  • Idempotent-Replayed"true"Optional
    Present and true when returning the stored outcome of an earlier execution.

    Example: true

  • Idempotency-Key-Released"true"Optional
    Present and true after a proven no-effect refusal and confirmed release. This attempt bound nothing; a concurrent request may acquire the key.

    Example: true

application/json

  • dataobjectRequired
    • Unknown properties are rejected

    Example: {"id":"00000000-0000-0000-0000-000000000004","invoice_id":"00000000-0000-0000-0000-000000000003","payout_id":"00000000-0000-0000-0000-000000000004","currency":"USDC","network":"ethereum","amount":"5.00","recipient_amount":"5.00","merchant_debit_amount":"5.10","refund_fee":"0.10","refund_fee_allocation":"merchant","network_fee":null,"configured_network_fee_amount":"0.00","network_fee_allocation":"platform","destination":"0x0000000000000000000000000000000000000001","destination_tag":null,"status":"pending","environment":"live","tx_hash":null,"failure_reason":null,"description":"Partial refund for order 1001","metadata":null,"completed_at":null,"created_at":"2026-09-14T10:00:00Z","payment_id":null,"refund_request_id":null}

    • idstringRequired
      Opaque refund identifier. Save and pass it unchanged to GET refund.
      • Maximum length: 80

      Example: string

    • invoice_idstringRequired
      Invoice UUID being refunded.
      • Format: uuid

      Example: 00000000-0000-0000-0000-000000000001

    • payment_idstring | nullRequired
      Original incoming payment. Null for invoice-level API refunds without payment attribution.
      • Format: uuid

      Example: null

    • refund_request_idstring | nullRequired
      Groups refunds authorized by one manual confirmation. Null for individually created API refunds.
      • Format: uuid

      Example: null

    • payout_idstringRequired
      Compatibility identifier equal to this refund's ID. It does not identify a separate payout.
      • Maximum length: 80

      Example: string

    • currencystringRequired
      Refunded asset currency.

      Example: string

    • networkstringRequired
      Refunded asset network.

      Example: string

    • amountstringRequired
      Base/requested refund amount as a decimal string.

      Example: 1

    • recipient_amountstringRequired
      Amount sent to the customer after customer-allocated commercial charges.
      • Format: decimal
      • Pattern: ^\d+(\.\d+)?$

      Example: 1

    • merchant_debit_amountstringRequired
      Authorized merchant debit including merchant-paid charges. For a payment not credited to the merchant, its held funds supply principal and this amount covers charges only. Failed or reversed execution can release or reverse the debit.
      • Format: decimal
      • Pattern: ^\d+(\.\d+)?$

      Example: 1

    • refund_feestringRequired
      Decimal string platform refund fee in refund currency.

      Example: 1

    • refund_fee_allocation"customer" | "merchant" | "platform"Required

      Example: customer

    • network_feestring | nullRequired
      Decimal string actual on-chain network fee when available; null when unavailable, including returns of funds that were not credited to the merchant balance.

      Example: null

    • configured_network_fee_amountstringRequired
      Snapshotted commercial network charge; distinct from actual on-chain cost.
      • Format: decimal
      • Pattern: ^\d+(\.\d+)?$

      Example: 1

    • network_fee_allocation"customer" | "merchant" | "platform"Required

      Example: customer

    • destinationstringRequired
      Destination blockchain address.

      Example: string

    • destination_tagstring | nullRequired
      Memo/tag if applicable.

      Example: null

    • status"pending" | "processing" | "confirming" | "completed" | "failed" | "reversed"Required
      Refund execution status.

      Example: pending

    • environment"live" | "test"Required
      Environment this refund belongs to.

      Example: live

    • tx_hashstring | nullRequired
      On-chain transaction hash after completion.

      Example: null

    • failure_reasonstring | nullRequired
      Merchant-facing reason when the refund fails. A rejected destination is reported as destination_rejected; other failures are reported as internal_error. Null while processing or after completion.

      Example: null

    • descriptionstring | nullRequired
      Refund description.

      Example: null

    • metadataobject | nullRequired
      Merchant-supplied metadata.

      Example: null

      • [key]stringOptional

        Example: string

    • completed_atstring | nullRequired
      Completion timestamp.
      • Format: date-time

      Example: null

    • created_atstringRequired
      Creation timestamp.
      • Format: date-time

      Example: 2026-01-01T00:00:00Z

  • metaobjectRequired
    • Unknown properties are rejected

    Example: {"request_id":"00000000-0000-0000-0000-000000000001"}

    • request_idstringRequired
      Correlation identifier issued by the API, also returned in the X-Request-ID response header. Client-supplied X-Request-ID values are replaced.

      Example: string

201 response
{
  "data": {
    "id": "00000000-0000-0000-0000-000000000004",
    "invoice_id": "00000000-0000-0000-0000-000000000003",
    "payout_id": "00000000-0000-0000-0000-000000000004",
    "currency": "USDC",
    "network": "ethereum",
    "amount": "5.00",
    "recipient_amount": "5.00",
    "merchant_debit_amount": "5.10",
    "refund_fee": "0.10",
    "refund_fee_allocation": "merchant",
    "network_fee": null,
    "configured_network_fee_amount": "0.00",
    "network_fee_allocation": "platform",
    "destination": "0x0000000000000000000000000000000000000001",
    "destination_tag": null,
    "status": "pending",
    "environment": "live",
    "tx_hash": null,
    "failure_reason": null,
    "description": "Partial refund for order 1001",
    "metadata": null,
    "completed_at": null,
    "created_at": "2026-09-14T10:00:00Z",
    "payment_id": null,
    "refund_request_id": null
  },
  "meta": {
    "request_id": "00000000-0000-0000-0000-000000000001"
  }
}

400 `validation_error`: The request format, parameters, headers, or field values are invalid.

  • Idempotent-Replayed"true"Optional
    Present and true when returning the stored outcome of an earlier execution.

    Example: true

  • Idempotency-Key-Released"true"Optional
    Present and true after a proven no-effect refusal and confirmed release. This attempt bound nothing; a concurrent request may acquire the key.

    Example: true

application/json

  • errorobjectRequired
    The error code and diagnostic message.
    • Unknown properties are rejected

    Example: {"code":"validation_error","message":"string","details":["string"]}

    • code"validation_error"Required
      `validation_error`: The request format, parameters, headers, or field values are invalid.

      Example: validation_error

    • messagestringRequired
      Human-readable description of the error.

      Example: string

    • detailsarray<string>Optional
      Optional structured context (validation errors, etc.).

      Example: ["string"]

  • metaobjectRequired
    • Unknown properties are rejected

    Example: {"request_id":"00000000-0000-0000-0000-000000000001"}

    • request_idstringRequired
      Correlation identifier issued by the API, also returned in the X-Request-ID response header. Client-supplied X-Request-ID values are replaced.

      Example: string

400 response
{
  "error": {
    "code": "validation_error",
    "message": "Request validation failed"
  },
  "meta": {
    "request_id": "00000000-0000-0000-0000-000000000001"
  }
}

401 `unauthorized`: A valid API key is required.

  • Idempotent-Replayed"true"Optional
    Present and true when returning the stored outcome of an earlier execution.

    Example: true

  • Idempotency-Key-Released"true"Optional
    Present and true after a proven no-effect refusal and confirmed release. This attempt bound nothing; a concurrent request may acquire the key.

    Example: true

application/json

  • errorobjectRequired
    The error code and diagnostic message.
    • Unknown properties are rejected

    Example: {"code":"unauthorized","message":"string","details":["string"]}

    • code"unauthorized"Required
      `unauthorized`: A valid API key is required.

      Example: unauthorized

    • messagestringRequired
      Human-readable description of the error.

      Example: string

    • detailsarray<string>Optional
      Optional structured context (validation errors, etc.).

      Example: ["string"]

  • metaobjectRequired
    • Unknown properties are rejected

    Example: {"request_id":"00000000-0000-0000-0000-000000000001"}

    • request_idstringRequired
      Correlation identifier issued by the API, also returned in the X-Request-ID response header. Client-supplied X-Request-ID values are replaced.

      Example: string

401 response
{
  "error": {
    "code": "unauthorized",
    "message": "Invalid API key"
  },
  "meta": {
    "request_id": "00000000-0000-0000-0000-000000000001"
  }
}

403 `forbidden`: The API key does not have permission for this operation. `api_key_ip_forbidden`: The request source address is not allowed by the API key.

  • Idempotent-Replayed"true"Optional
    Present and true when returning the stored outcome of an earlier execution.

    Example: true

  • Idempotency-Key-Released"true"Optional
    Present and true after a proven no-effect refusal and confirmed release. This attempt bound nothing; a concurrent request may acquire the key.

    Example: true

application/json

  • errorobjectRequired
    The error code and diagnostic message.
    • Unknown properties are rejected

    Example: {"code":"forbidden","message":"string","details":["string"]}

    • code"forbidden" | "api_key_ip_forbidden"Required
      `forbidden`: The API key does not have permission for this operation. `api_key_ip_forbidden`: The request source address is not allowed by the API key.

      Example: forbidden

    • messagestringRequired
      Human-readable description of the error.

      Example: string

    • detailsarray<string>Optional
      Optional structured context (validation errors, etc.).

      Example: ["string"]

  • metaobjectRequired
    • Unknown properties are rejected

    Example: {"request_id":"00000000-0000-0000-0000-000000000001"}

    • request_idstringRequired
      Correlation identifier issued by the API, also returned in the X-Request-ID response header. Client-supplied X-Request-ID values are replaced.

      Example: string

403 response
{
  "error": {
    "code": "forbidden",
    "message": "Permission denied"
  },
  "meta": {
    "request_id": "00000000-0000-0000-0000-000000000001"
  }
}

404 `not_found`: The invoice was not found in the API key's merchant and environment scope.

  • Idempotent-Replayed"true"Optional
    Present and true when returning the stored outcome of an earlier execution.

    Example: true

  • Idempotency-Key-Released"true"Optional
    Present and true after a proven no-effect refusal and confirmed release. This attempt bound nothing; a concurrent request may acquire the key.

    Example: true

application/json

  • errorobjectRequired
    The error code and diagnostic message.
    • Unknown properties are rejected

    Example: {"code":"not_found","message":"string","details":["string"]}

    • code"not_found"Required
      `not_found`: The invoice was not found in the API key's merchant and environment scope.

      Example: not_found

    • messagestringRequired
      Human-readable description of the error.

      Example: string

    • detailsarray<string>Optional
      Optional structured context (validation errors, etc.).

      Example: ["string"]

  • metaobjectRequired
    • Unknown properties are rejected

    Example: {"request_id":"00000000-0000-0000-0000-000000000001"}

    • request_idstringRequired
      Correlation identifier issued by the API, also returned in the X-Request-ID response header. Client-supplied X-Request-ID values are replaced.

      Example: string

404 response
{
  "error": {
    "code": "not_found",
    "message": "Resource not found"
  },
  "meta": {
    "request_id": "00000000-0000-0000-0000-000000000001"
  }
}

409 `conflict`: The idempotency key is bound to a different request, or this request conflicts with an existing resource. `in_flight`: A request with this idempotency key is still executing. Retry the original request with the same key.

  • Idempotent-Replayed"true"Optional
    Present and true when returning the stored outcome of an earlier execution.

    Example: true

  • Idempotency-Key-Released"true"Optional
    Present and true after a proven no-effect refusal and confirmed release. This attempt bound nothing; a concurrent request may acquire the key.

    Example: true

application/json

  • errorobjectRequired
    The error code and diagnostic message.
    • Unknown properties are rejected

    Example: {"code":"conflict","message":"string","details":["string"]}

    • code"conflict" | "in_flight"Required
      `conflict`: The idempotency key is bound to a different request, or this request conflicts with an existing resource. `in_flight`: A request with this idempotency key is still executing. Retry the original request with the same key.

      Example: conflict

    • messagestringRequired
      Human-readable description of the error.

      Example: string

    • detailsarray<string>Optional
      Optional structured context (validation errors, etc.).

      Example: ["string"]

  • metaobjectRequired
    • Unknown properties are rejected

    Example: {"request_id":"00000000-0000-0000-0000-000000000001"}

    • request_idstringRequired
      Correlation identifier issued by the API, also returned in the X-Request-ID response header. Client-supplied X-Request-ID values are replaced.

      Example: string

409 response
{
  "error": {
    "code": "conflict",
    "message": "Request conflicts with the existing state"
  },
  "meta": {
    "request_id": "00000000-0000-0000-0000-000000000001"
  }
}

422 `unprocessable_entity`: The invoice, refund amount, currency/network pair, destination, or balance does not allow this refund. `gate_unknown`: The requested currency and network pair is unknown.

  • Idempotent-Replayed"true"Optional
    Present and true when returning the stored outcome of an earlier execution.

    Example: true

  • Idempotency-Key-Released"true"Optional
    Present and true after a proven no-effect refusal and confirmed release. This attempt bound nothing; a concurrent request may acquire the key.

    Example: true

application/json

  • errorobjectRequired
    The error code and diagnostic message.
    • Unknown properties are rejected

    Example: {"code":"unprocessable_entity","message":"string","details":["string"]}

    • code"unprocessable_entity" | "gate_unknown"Required
      `unprocessable_entity`: The invoice, refund amount, currency/network pair, destination, or balance does not allow this refund. `gate_unknown`: The requested currency and network pair is unknown.

      Example: unprocessable_entity

    • messagestringRequired
      Human-readable description of the error.

      Example: string

    • detailsarray<string>Optional
      Optional structured context (validation errors, etc.).

      Example: ["string"]

  • metaobjectRequired
    • Unknown properties are rejected

    Example: {"request_id":"00000000-0000-0000-0000-000000000001"}

    • request_idstringRequired
      Correlation identifier issued by the API, also returned in the X-Request-ID response header. Client-supplied X-Request-ID values are replaced.

      Example: string

422 response
{
  "error": {
    "code": "unprocessable_entity",
    "message": "The request cannot be processed in the current state"
  },
  "meta": {
    "request_id": "00000000-0000-0000-0000-000000000001"
  }
}

500 `internal_error`: The API could not complete the request. Contact support with the request ID if the error persists.

  • Idempotent-Replayed"true"Optional
    Present and true when returning the stored outcome of an earlier execution.

    Example: true

  • Idempotency-Key-Released"true"Optional
    Present and true after a proven no-effect refusal and confirmed release. This attempt bound nothing; a concurrent request may acquire the key.

    Example: true

application/json

  • errorobjectRequired
    The error code and diagnostic message.
    • Unknown properties are rejected

    Example: {"code":"internal_error","message":"string","details":["string"]}

    • code"internal_error"Required
      `internal_error`: The API could not complete the request. Contact support with the request ID if the error persists.

      Example: internal_error

    • messagestringRequired
      Human-readable description of the error.

      Example: string

    • detailsarray<string>Optional
      Optional structured context (validation errors, etc.).

      Example: ["string"]

  • metaobjectRequired
    • Unknown properties are rejected

    Example: {"request_id":"00000000-0000-0000-0000-000000000001"}

    • request_idstringRequired
      Correlation identifier issued by the API, also returned in the X-Request-ID response header. Client-supplied X-Request-ID values are replaced.

      Example: string

500 response
{
  "error": {
    "code": "internal_error",
    "message": "Internal server error"
  },
  "meta": {
    "request_id": "00000000-0000-0000-0000-000000000001"
  }
}

503 `service_unavailable`: The service is temporarily unavailable. If the outcome is uncertain, retry the original request with the same idempotency key.

  • Idempotency-Key-Released"true"Optional
    Present and true after a proven no-effect refusal and confirmed release. This attempt bound nothing; a concurrent request may acquire the key.

    Example: true

  • Idempotent-Replayed"true"Optional
    Present and true when returning the stored outcome of an earlier execution.

    Example: true

application/json

  • errorobjectRequired
    The error code and diagnostic message.
    • Unknown properties are rejected

    Example: {"code":"service_unavailable","message":"string","details":["string"]}

    • code"service_unavailable"Required
      `service_unavailable`: The service is temporarily unavailable. If the outcome is uncertain, retry the original request with the same idempotency key.

      Example: service_unavailable

    • messagestringRequired
      Human-readable description of the error.

      Example: string

    • detailsarray<string>Optional
      Optional structured context (validation errors, etc.).

      Example: ["string"]

  • metaobjectRequired
    • Unknown properties are rejected

    Example: {"request_id":"00000000-0000-0000-0000-000000000001"}

    • request_idstringRequired
      Correlation identifier issued by the API, also returned in the X-Request-ID response header. Client-supplied X-Request-ID values are replaced.

      Example: string

503 response
{
  "error": {
    "code": "service_unavailable",
    "message": "Service temporarily unavailable"
  },
  "meta": {
    "request_id": "00000000-0000-0000-0000-000000000001"
  }
}