# Save a payment method

`POST https://api.weavpayments.com/v1/payment-methods`

Saves a customer's card for later charges with no payer present (auto-debit, subscriptions). Send your customer to `next_action.checkout_url`: Weav's save page, where they type their card, agree to future charges and confirm it with their bank (3DS). Their bank may show a ₱10.00 check that is canceled right after. The card goes from their browser to the card processor; it never reaches Weav or you.

You get `PAYMENT_METHOD_SAVED` (now `ACTIVE`: charge it with `POST /v1/payments` and its `payment_method_id`) or `PAYMENT_METHOD_FAILED`. A link not used within 24 hours fails. Only cards today (`type: CARD`).

Saving cards is off until Weav turns it on for your account with your PCI DSS evidence (422 `4220401`); in test mode it always works, with a test save page.

## Headers

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

## Body

JSON, with `Content-Type: application/json`.

- `type` (string, required): One of: `CARD`.
- `customer` (object, required)
  - `id` (string, required): Your own ID for the customer. List their saved methods with it. Up to 64 characters. Pattern: ^[A-Za-z0-9._:@-]+$.
  - `name` (string | null): Weav's save page asks your customer when you leave it out. Up to 255 characters.
  - `email` (string (email) | null): Up to 255 characters.
- `success_url` (string (uri) | null): HTTPS. Where your customer goes once it's saved. Up to 2048 characters.
- `cancel_url` (string (uri) | null): HTTPS. Where your customer goes if they leave, or the bank's check fails. Up to 2048 characters.
- `webhook_url` (string | null): This payment method's webhooks go here instead of your webhook URL. Up to 2048 characters.

## Responses

### 201 Created

Payment method created (code 2010400).

What `data` holds:

- `payment_method` (object)
  - `id` (string)
  - `type` (string): One of: `CARD`.
  - `status` (string): `PENDING` until your customer saves it and their bank confirms it; `ACTIVE` (chargeable); `FAILED` when that never happens; `REMOVED`. One of: `PENDING`, `ACTIVE`, `FAILED`, `REMOVED`.
  - `customer` (object)
    - `id` (string)
    - `name` (string | null)
    - `email` (string | null)
  - `card` (object | null): Once saved. Never the card number.
    - `brand` (string): From the card processor. One of: `VISA`, `MASTERCARD`, `JCB`.
    - `last4` (string)
    - `exp_month` (integer | null): The expiry month your customer typed. Ask them for a new card before it passes (`expires_by` on the list). At least 1. At most 12.
    - `exp_year` (integer | null): Four digits.
  - `label` (string | null): To show your customer.
  - `next_action` (object)
    - `type` (string | null): One of: `REDIRECT`.
    - `checkout_url` (string (uri) | null): Weav's save page, while `PENDING`.
  - `success_url` (string (uri) | null)
  - `cancel_url` (string (uri) | null)
  - `webhook_url` (string | null)
  - `expires_at` (string (date-time) | null): When the save link ends, while `PENDING`.
  - `activated_at` (string (date-time) | null)
  - `failed_at` (string (date-time) | null)
  - `removed_at` (string (date-time) | null)
  - `created_at` (string (date-time))
  - `updated_at` (string (date-time))

```json
{
    "code": "2010400",
    "success": true,
    "message": "Payment method created",
    "data": {
        "payment_method": {
            "id": "PM8K2Q7X9KBAT4MZ3N",
            "type": "CARD",
            "status": "PENDING",
            "customer": {
                "id": "USER-123",
                "name": "Juan dela Cruz",
                "email": "juan@example.com"
            },
            "card": null,
            "label": null,
            "next_action": {
                "type": "REDIRECT",
                "checkout_url": "https://pay.weavpayments.com/save/PM8K2Q7X9KBAT4MZ3N"
            },
            "success_url": "https://shop.example/account/cards",
            "cancel_url": "https://shop.example/account",
            "webhook_url": null,
            "expires_at": "2026-11-03T10:12:00+08:00",
            "activated_at": null,
            "failed_at": null,
            "removed_at": null,
            "created_at": "2026-11-02T10:12:00+08:00",
            "updated_at": "2026-11-02T10:12: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."
}
```

### 422 Unprocessable Content

Invalid fields, or saving cards is off for your account (4220401).

```json
{
    "code": "4220401",
    "success": false,
    "message": "Saving cards is off for your account.",
    "errors": {
        "type": [
            "Contact support@weavpayments.com to turn it on. Weav needs your PCI DSS evidence first."
        ]
    }
}
```

## Examples

cURL:

```shell
curl -X POST https://api.weavpayments.com/v1/payment-methods \
    -H "Authorization: Bearer $WEAV_API_KEY" \
    -H "Content-Type: application/json" \
    -d '{
    "type": "CARD",
    "customer": {
        "id": "USER-123",
        "name": "Juan dela Cruz",
        "email": "juan@example.com"
    },
    "success_url": "https://shop.example/account/cards",
    "cancel_url": "https://shop.example/account"
}'
```

Node.js:

```js
const url = 'https://api.weavpayments.com/v1/payment-methods';
const response = await fetch(url, {
    method: 'POST',
    headers: {
        Authorization: `Bearer ${process.env.WEAV_API_KEY}`,
        'Content-Type': 'application/json',
    },
    body: JSON.stringify({
        type: 'CARD',
        customer: {
            id: 'USER-123',
            name: 'Juan dela Cruz',
            email: 'juan@example.com',
        },
        success_url: 'https://shop.example/account/cards',
        cancel_url: 'https://shop.example/account',
    }),
});

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

JavaScript:

```js
import axios from 'axios';

const response = await axios.request({
    method: 'post',
    url: 'https://api.weavpayments.com/v1/payment-methods',
    headers: {
        Authorization: `Bearer ${process.env.WEAV_API_KEY}`,
    },
    data: {
        type: 'CARD',
        customer: {
            id: 'USER-123',
            name: 'Juan dela Cruz',
            email: 'juan@example.com',
        },
        success_url: 'https://shop.example/account/cards',
        cancel_url: 'https://shop.example/account',
    },
    // 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/payment-methods', [
        'type' => 'CARD',
        'customer' => [
            'id' => 'USER-123',
            'name' => 'Juan dela Cruz',
            'email' => 'juan@example.com',
        ],
        'success_url' => 'https://shop.example/account/cards',
        'cancel_url' => 'https://shop.example/account',
    ]);

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

Python:

```python
import os
import requests

response = requests.post(
    "https://api.weavpayments.com/v1/payment-methods",
    headers={
        "Authorization": f"Bearer {os.environ['WEAV_API_KEY']}",
    },
    json={
        "type": "CARD",
        "customer": {
            "id": "USER-123",
            "name": "Juan dela Cruz",
            "email": "juan@example.com",
        },
        "success_url": "https://shop.example/account/cards",
        "cancel_url": "https://shop.example/account",
    },
    timeout=30,
)

result = response.json()
```

Go:

```go
package main

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

func main() {
    body := strings.NewReader(`{
    "type": "CARD",
    "customer": {
        "id": "USER-123",
        "name": "Juan dela Cruz",
        "email": "juan@example.com"
    },
    "success_url": "https://shop.example/account/cards",
    "cancel_url": "https://shop.example/account"
}`)

    req, err := http.NewRequest("POST", "https://api.weavpayments.com/v1/payment-methods", body)
    if err != nil {
        panic(err)
    }
    req.Header.Set("Authorization", "Bearer "+os.Getenv("WEAV_API_KEY"))
    req.Header.Set("Content-Type", "application/json")

    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"
use serde_json::json;

fn main() -> Result<(), Box<dyn std::error::Error>> {
    let key = std::env::var("WEAV_API_KEY")?;
    let body = json!({
        "type": "CARD",
        "customer": {
            "id": "USER-123",
            "name": "Juan dela Cruz",
            "email": "juan@example.com"
        },
        "success_url": "https://shop.example/account/cards",
        "cancel_url": "https://shop.example/account"
    });

    let response = reqwest::blocking::Client::new()
        .post("https://api.weavpayments.com/v1/payment-methods")
        .bearer_auth(key)
        .json(&body)
        .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/payment-methods";
        String body =
                """
                {
                    "type": "CARD",
                    "customer": {
                        "id": "USER-123",
                        "name": "Juan dela Cruz",
                        "email": "juan@example.com"
                    },
                    "success_url": "https://shop.example/account/cards",
                    "cancel_url": "https://shop.example/account"
                }
                """;

        HttpRequest request =
                HttpRequest.newBuilder()
                        .uri(URI.create(url))
                        .header("Authorization", "Bearer " + System.getenv("WEAV_API_KEY"))
                        .header("Content-Type", "application/json")
                        .POST(HttpRequest.BodyPublishers.ofString(body))
                        .build();

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