Skip to main content

Refund Contract Funds

After a contract has reached the Escrow status, or a Dispute has been raised, you can return funds to the buyer using the refund API.

Webhook events for refunds are documented in Webhook Notifications.


Unified Refund Endpoint​

All refund operations are handled by a single endpoint. The request body determines whether the refund targets the whole contract or a specific milestone.

POST /apps/api/contracts/{externalContractId}/refund

Path Parameters

FieldTypeDescriptionRequired / Notes / Example
externalContractIdstringThe external contract identifierRequired
Example: CNT-2604-00100002

Headers

  • Authorization: Bearer {access_token}
  • Content-Type: application/json

Refund Request Body​

Field NameTypeDescriptionRequired / Notes
milestoneIdintegerWhen provided, the refund targets a specific milestoneRequired if contract has milestones
reasonstringMandatory reason for the refund, kept for audit trailRequired
Max 500 chars
important

The buyer's IBAN and account holder name are not part of the refund request. WePay resolves them automatically from the verified buyer record.


Buyer Eligibility and IBAN Resolution​

Before any refund is created, WePay runs a pre-flight check against the buyer record:

  1. Authorization: The buyer must have completed the WePay authorization step.
  2. Bank account verification: When the platform requires a verified IBAN for the buyer, the buyer must have a verified bank account.
  3. IBAN resolution: Once eligibility passes, WePay resolves the buyer IBAN and account holder name from the verified record.

If buyer authorization or bank verification is missing, WePay rejects the refund and sends an onboarding SMS to the buyer when possible.

ConditionHTTPMessage key
Buyer has not completed authorization400BuyerNotAuthorized
Buyer's bank account is not verified400BuyerBankAccountNotVerified
Buyer record not found for this business404ExternalUserNotFound
IBAN could not be resolved from the verified record500FailedToResolveIban

Refund Preconditions​

ScopeRequired contract statusRequired milestone status
Contract refundEscrow or Dispute--
Milestone refundEscrow or DisputeEscrow
  • Only one active refund of a given type is allowed per target. Any additional refund request of the same type is rejected.
  • When a refund request is created, the contract transitions to RefundInProgress.
  • If the refund is milestone-scoped, the milestone also transitions to RefundInProgress.
  • When the refund succeeds, the final status becomes Refunded. But if the refund targets a milestone, the contract status will be Cancelled.

Refund β€” Contract​

Returns the entire escrowed contract principal to the buyer. WePay platform fees and taxes are retained.

Example Request​

curl --location 'https://api.wepay.com.sa/apps/api/contracts/CNT-2604-00100002/refund' \
--header 'Authorization: Bearer YOUR_ACCESS_TOKEN' \
--header 'Content-Type: application/json' \
--data '{
"milestoneId": 9,
"reason": "Order cancelled by buyer; goods never shipped."
}'

Example Response​

{
"data": {
"refundId": 4521,
"settlementId": null,
"milestoneId": null,
"refundedAmount": 1000.00,
"releasedToSellerAmount": null,
"type": "FullRefund",
"externalContractId": "CNT-2604-00100002"
},
"message": "RefundCreatedSuccessfully",
"status": 200,
"validationErrors": []
}

Full Refund β€” Milestone​

Returns the entire escrowed amount of a single milestone to the buyer.

Example Request​

curl --location 'https://api.wepay.com.sa/apps/api/contracts/CNT-2604-00100002/refund' \
--header 'Authorization: Bearer YOUR_ACCESS_TOKEN' \
--header 'Content-Type: application/json' \
--data '{
"milestoneId": 13,
"reason": "Milestone deliverable rejected; refund agreed."
}'

Example Response​

{
"data": {
"refundId": 4523,
"settlementId": null,
"milestoneId": 13,
"refundedAmount": 400.00,
"releasedToSellerAmount": null,
"type": "FullRefund",
"externalContractId": "CNT-2604-00100002"
},
"message": "RefundCreatedSuccessfully",
"status": 200,
"validationErrors": []
}

Refund Response Fields​

All refund operations return the same unified response.

FieldTypeDescription
refundIdintegerInternal WePay refund identifier. Use it to correlate with refund webhooks
settlementIdinteger / nullSettlement identifier for seller release portion. Only for partial refunds
milestoneIdinteger / nullRefunded milestone ID. Only for milestone-scoped refunds
refundedAmountnumberAmount returned to buyer
releasedToSellerAmountnumber / nullNet amount released to seller. Only for partial refunds
typestring"FullRefund"
externalContractIdstringExternal contract id

Refund Status Transitions​

EventContract statusMilestone status (if applicable)
Refund initiatedRefundInProgressRefundInProgress

HTTP Status Codes​

StatusDescription
200Refund initiated successfully
400Validation failed, invalid status, invalid amount, active refund already exists, buyer not authorized, or buyer bank account not verified
401Unauthorized
403Caller is not authorized to refund this contract
404Contract, milestone, or buyer record not found
409Concurrent refund race condition detected
500Buyer IBAN could not be resolved from the verified record