# Cancel a payment

`POST https://api.weavpayments.com/v1/payments/{reference_id}/cancel`

Cancels a payment that's still `PENDING`, so the customer can no longer pay it. Proposed.

Any status other than `PENDING` returns `409` with code `4090101`. A completed payment needs a [refund](https://docs.weavpayments.com/refunds) instead.

## Path parameters

- `reference_id` (string, required): Weav's payment reference. Pattern: ^PY[0-9A-Z]{12}$.

## Headers

- `Authorization` (string, required): `Bearer` plus your API key. See [Authentication](https://docs.weavpayments.com/authentication).

## Responses

### 200 OK

Payment canceled (code 2000101).

What `data` holds:

- `payment` (object)
  - `reference_id` (string)
  - `external_reference_id` (string | null): Yours. For a checkout's payment, the checkout's (null when it has none).
  - `checkout_reference_id` (string | null): The checkout this payment was started from, if any.
  - `payment_method_id` (string | null): The saved payment method it was charged to, or the one it saves the card to (`save_payment_method`), if any.
  - `provider_reference_id` (string | null): The provider's transaction ID, once the provider has one.
  - `channel_reference_id` (string | null): A reference from the channel that is unique to this payment, once the payment completes. Null for channels whose numbers can repeat; those are in references.
  - `references` (object): The provider's other numbers for this payment, keyed by type: `invoice_number` (the 6-digit QR Ph invoice number), `receipt_number` (Maya) or `transaction_id` (GCash). Saved once the provider confirms the payment; {} until then. They can repeat across payments: search with them, never identify a payment by one alone.
    - `invoice_number` (string)
    - `receipt_number` (string)
    - `transaction_id` (string)
  - `channel` (string): One of `QR_PH`, `GCASH`, `MAYA`, `CARD`, `PAY_AND_GO`, `PALAWANPAY`, `BAYAD`. One of: `QR_PH`, `GCASH`, `MAYA`, `CARD`, `PAY_AND_GO`, `PALAWANPAY`, `BAYAD`.
  - `customer` (object): The person paying, as you sent it. The email and mobile number turn null once Weav's retention period ends.
    - `name` (string | null)
    - `email` (string | null)
    - `mobile_no` (string | null)
  - `currency` (string): ISO 4217. PHP today; more as they're enabled for your account. One of: `PHP`.
  - `amount` (number)
  - `fee` (number | null): Null until a rate applies.
  - `total` (number | null): `amount` plus `fee`, calculated by Weav. Null while `fee` is null.
  - `fee_bearer` (string): `MERCHANT`, the fee comes out of `amount` (default). `PAYER` (fees on top), the customer is charged `amount` + `fee` and you receive the full `amount`. One of: `MERCHANT`, `PAYER`.
  - `amount_charged` (number): What the customer is charged. `amount`, or `total` when `fee_bearer` is `PAYER`.
  - `amount_refunded` (number): The total refunded so far. The payment turns `REFUNDED` once it equals `amount`.
  - `refunds` (array of objects): Refunds Weav recorded for this payment, oldest first. Refunds are made by Weav support on request.
    - `reference_id` (string)
    - `amount` (number)
    - `provider_reference_id` (string | null)
    - `refunded_at` (string (date-time))
  - `status` (string): `PENDING`, `COMPLETED`, `FAILED`, `CANCELLED`, `REVERSED` or `REFUNDED`. See [Statuses](https://docs.weavpayments.com/statuses). One of: `PENDING`, `COMPLETED`, `FAILED`, `CANCELLED`, `REVERSED`, `REFUNDED`.
  - `description` (string | null): As you sent it.
  - `success_url` (string (uri) | null)
  - `cancel_url` (string (uri) | null)
  - `webhook_url` (string | null): Where this payment's webhooks go, when not your webhook URL.
  - `next_action` (object): What your customer does next. `type` is `DISPLAY_QR`, `REDIRECT`, `PAY_AT_COUNTER`, or null once the payment is final. `qr_string`, `checkout_url` and `payment_code` are each null unless they apply. With `HOSTED` (the default), `type` is `REDIRECT` and `checkout_url` is Weav's payment page (the test checkout with a test key), for every channel. For QR Ph, `qr_ph_app` is the app you chose, and `instructions` lists the how-to-pay steps (`title`, `description`) when you asked for `DIRECT`.
    - `type` (string | null): One of: `DISPLAY_QR`, `REDIRECT`, `PAY_AT_COUNTER`.
    - `qr_string` (string | null): QR Ph payload to render as a QR code: error correction Q (M at minimum), a white quiet zone of at least 4 modules, and the full-color QR Ph symbol on a white box 23.81% of the code width (quiet zone excluded) in the exact center.
    - `checkout_url` (string (uri) | null)
    - `payment_code` (string | null): For over-the-counter channels.
    - `qr_ph_app` (string | null): QR Ph only. The bank or e-wallet app the steps are tailored to.
    - `instructions` (array of objects | null): QR Ph with `next_action_format` `DIRECT` only. How-to-pay steps to show the customer.
      - `title` (string)
      - `description` (string)
  - `test_checkout_url` (string (uri) | null): Test mode only: the test checkout page, where you pay, decline or let this payment expire, for every channel (a counter payment with `DIRECT` has no other way to finish). Anyone with the link can open it. Null in live mode.
  - `failure` (object | null): `code` and `message` when the payment failed. Codes: `EXPIRED`, `DECLINED`, `PROVIDER_ERROR`.
    - `code` (string): One of: `EXPIRED`, `DECLINED`, `PROVIDER_ERROR`.
    - `message` (string)
  - `expires_at` (string (date-time)): When an unpaid payment fails (`failure.code` `EXPIRED`).
  - `completed_at` (string (date-time) | null): When the customer paid, as the provider reports it.
  - `failed_at` (string (date-time) | null): When the payment failed.
  - `cancelled_at` (string (date-time) | null): When the payment was canceled.
  - `reversed_at` (string (date-time) | null): When the payment was reversed.
  - `created_at` (string (date-time)): When Weav created the payment.
  - `updated_at` (string (date-time)): When the payment last changed.

```json
{
    "code": "2000101",
    "success": true,
    "message": "Payment canceled",
    "data": {
        "payment": {
            "reference_id": "PY9M4C2X7K1VHE",
            "external_reference_id": "ORD-10543",
            "checkout_reference_id": null,
            "payment_method_id": null,
            "provider_reference_id": null,
            "channel_reference_id": null,
            "references": {},
            "channel": "PAY_AND_GO",
            "customer": {
                "name": "Juan dela Cruz",
                "email": null,
                "mobile_no": null
            },
            "currency": "PHP",
            "amount": 3500.0,
            "fee": 87.5,
            "total": 3587.5,
            "fee_bearer": "MERCHANT",
            "amount_charged": 3500.0,
            "amount_refunded": 0.0,
            "refunds": [],
            "status": "CANCELLED",
            "description": null,
            "success_url": null,
            "cancel_url": null,
            "webhook_url": null,
            "next_action": {
                "type": null,
                "qr_string": null,
                "checkout_url": null,
                "payment_code": null,
                "qr_ph_app": null,
                "instructions": null
            },
            "test_checkout_url": null,
            "failure": null,
            "expires_at": "2026-10-28T10:12:00+08:00",
            "completed_at": null,
            "failed_at": null,
            "cancelled_at": "2026-10-26T11:02:00+08:00",
            "reversed_at": null,
            "created_at": "2026-10-26T10:12:00+08:00",
            "updated_at": "2026-10-26T11:02:00+08:00"
        }
    }
}
```

### 401 Unauthorized

Missing, malformed or unknown API key (code 4010000).

```json
{
    "code": "4010000",
    "success": false,
    "message": "Missing or invalid API key."
}
```

### 403 Forbidden

The key can't make this request (code 4030000): an address outside the key's IP allowlist, a live key before your live mode is on, or an account that isn't active. A live key sent from a browser is refused (code 4030001): keep live keys on your server.

```json
{
    "code": "4030000",
    "success": false,
    "message": "Requests with this key must come from an address on its allowlist."
}
```

### 404 Not Found

Not found in this mode for your account (code 4040000).

```json
{
    "code": "4040000",
    "success": false,
    "message": "Not found."
}
```

### 409 Conflict

The payment isn't pending, so it can't be canceled (code 4090101), or the provider can't cancel it yet (code 4090102, retry shortly).

```json
{
    "code": "4090101",
    "success": false,
    "message": "Only a pending payment can be canceled."
}
```

### 501 Not Implemented

This channel isn't live for your account yet (code 5010100); use a test key.

```json
{
    "code": "5010100",
    "success": false,
    "message": "Live payments aren't available through the API yet. Use a test key, or contact support@weavpayments.com."
}
```

## Examples

cURL:

```shell
curl -X POST https://api.weavpayments.com/v1/payments/PY7K3Q9M2X4TBA/cancel \
    -H "Authorization: Bearer $WEAV_API_KEY"
```

Node.js:

```js
const url = 'https://api.weavpayments.com/v1/payments/PY7K3Q9M2X4TBA/cancel';
const response = await fetch(url, {
    method: 'POST',
    headers: {
        Authorization: `Bearer ${process.env.WEAV_API_KEY}`,
    },
});

const result = await response.json();
```

JavaScript:

```js
import axios from 'axios';

const response = await axios.request({
    method: 'post',
    url: 'https://api.weavpayments.com/v1/payments/PY7K3Q9M2X4TBA/cancel',
    headers: {
        Authorization: `Bearer ${process.env.WEAV_API_KEY}`,
    },
    // Read error answers too: their body has the error code.
    validateStatus: () => true,
});

const result = response.data;
```

PHP:

```php
use Illuminate\Support\Facades\Http;

$response = Http::withToken(config('services.weav.key'))
    ->post('https://api.weavpayments.com/v1/payments/PY7K3Q9M2X4TBA/cancel');

$result = $response->json();
```

Python:

```python
import os
import requests

response = requests.post(
    "https://api.weavpayments.com/v1/payments/PY7K3Q9M2X4TBA/cancel",
    headers={
        "Authorization": f"Bearer {os.environ['WEAV_API_KEY']}",
    },
    timeout=30,
)

result = response.json()
```

Go:

```go
package main

import (
    "fmt"
    "io"
    "net/http"
    "os"
)

func main() {
    req, err := http.NewRequest("POST", "https://api.weavpayments.com/v1/payments/PY7K3Q9M2X4TBA/cancel", nil)
    if err != nil {
        panic(err)
    }
    req.Header.Set("Authorization", "Bearer "+os.Getenv("WEAV_API_KEY"))

    res, err := http.DefaultClient.Do(req)
    if err != nil {
        panic(err)
    }
    defer res.Body.Close()

    result, err := io.ReadAll(res.Body)
    if err != nil {
        panic(err)
    }
    fmt.Println(res.Status, string(result))
}
```

Rust:

```rust
// Cargo.toml: reqwest = { version = "0.12", features = ["blocking", "json"] }, serde_json = "1"
fn main() -> Result<(), Box<dyn std::error::Error>> {
    let key = std::env::var("WEAV_API_KEY")?;

    let response = reqwest::blocking::Client::new()
        .post("https://api.weavpayments.com/v1/payments/PY7K3Q9M2X4TBA/cancel")
        .bearer_auth(key)
        .send()?;

    let status = response.status();
    let result: serde_json::Value = response.json()?;
    println!("{status} {result:#}");
    Ok(())
}
```

Java:

```java
import java.net.URI;
import java.net.http.HttpClient;
import java.net.http.HttpRequest;
import java.net.http.HttpResponse;

public class WeavExample {
    public static void main(String[] args) throws Exception {
        String url = "https://api.weavpayments.com/v1/payments/PY7K3Q9M2X4TBA/cancel";
        HttpRequest request =
                HttpRequest.newBuilder()
                        .uri(URI.create(url))
                        .header("Authorization", "Bearer " + System.getenv("WEAV_API_KEY"))
                        .method("POST", HttpRequest.BodyPublishers.noBody())
                        .build();

        HttpResponse<String> response =
                HttpClient.newHttpClient().send(request, HttpResponse.BodyHandlers.ofString());
        System.out.println(response.statusCode() + " " + response.body());
    }
}
```
