> For the complete documentation index, see [llms.txt](https://docs.coda.co/llms.txt). Markdown versions of documentation pages are available by appending `.md` to page URLs; this page is available as [Markdown](https://docs.coda.co/codapay/recurring-payments/submit-charge-with-tokenized-payment-method.md).

# Submit Charge with Tokenized Payment Method

Charge a saved payment method with the customer absent. Call this endpoint only from your **backend**, from an allowlisted IP.

### Pre-requisite

When integrating with Codapay APIs, ensure you adhere to the following standards:

1. Use HTTPS protocol.
2. TLS 1.2 or higher

<table><thead><tr><th width="133.35546875">Environment</th><th>Base URL</th></tr></thead><tbody><tr><td>Sandbox</td><td><code>https://sandbox.codapayments.com/airtime</code></td></tr><tr><td>Production</td><td><code>https://airtime.codapayments.com/airtime</code></td></tr></tbody></table>

### **Endpoint**

<mark style="color:$success;">**POST**</mark> ***{Base URL}*****/v1/payment/token/charge**

#### Headers <a href="#headers" id="headers"></a>

<table><thead><tr><th width="193.3558349609375">Header</th><th width="112.3621826171875">Required</th><th>Description</th></tr></thead><tbody><tr><td><code>Content-Type</code></td><td>yes</td><td><code>application/json; charset=UTF-8</code></td></tr><tr><td><code>X-API-Key</code></td><td>yes</td><td>Same value as Init v2 <code>apiKey</code>. Identifier only — not the JWT HMAC secret.</td></tr><tr><td><code>Authorization</code></td><td>yes</td><td><code>Bearer &#x3C;JWT></code> (HS256). See Authentication.</td></tr><tr><td><code>Idempotency-Key</code></td><td>yes</td><td>Unique identifier of the request to avoid duplicate charges. </td></tr></tbody></table>

{% hint style="warning" %}
Idempotency-Key implementation helps to ensure your order doesn't get charged twice unintentionally.&#x20;

1. Send a unique `Idempotency-Key` per logical charge attempt (UUID recommended).
2. Retry only the **same** request body (amount, `payment_method_id`, order identifiers, etc.) with the **same** key after timeouts.
3. **Stop retrying the same key before the 24-hour boundary.** After TTL expiry, the platform may treat the request as a new charge.
   {% endhint %}

#### Request parameters <a href="#request-parameters" id="request-parameters"></a>

<table><thead><tr><th width="158.65478515625">Field</th><th width="101.780517578125">Type</th><th width="93.3363037109375">Required</th><th>Description</th></tr></thead><tbody><tr><td><code>country</code></td><td>number</td><td>yes</td><td>Numeric country code. Must match JWT <code>country</code>.</td></tr><tr><td><code>currency</code></td><td>number</td><td>yes</td><td>Numeric currency code. Must match JWT <code>currency</code>.</td></tr><tr><td><code>payType</code></td><td>number</td><td>yes</td><td>Payment Channel Id. Must match JWT <code>payType</code>.</td></tr><tr><td><code>projectId</code></td><td>number</td><td>yes</td><td>Same project as Init. Must match JWT <code>projectId</code>.</td></tr><tr><td><code>orderId</code></td><td>string</td><td>yes</td><td>Your unique order id for <strong>this</strong> charge. Must match JWT <code>orderId</code>.</td></tr><tr><td><code>offSessionType</code></td><td>string</td><td>yes</td><td>Same agreement type as the CIT that saved the card. One of <code>Unscheduled</code>, <code>Subscriptions</code>, <code>Installments</code>. Must match JWT <code>offSessionType</code>.</td></tr><tr><td><code>items</code></td><td>array</td><td>yes</td><td>Non-empty. Each item: <code>code</code>, <code>price</code>, <code>name</code>. Must match JWT <code>items</code> exactly.</td></tr><tr><td><code>paymentMethodId</code></td><td>string</td><td>yes</td><td>Saved payment method id from <code>PAYMENT_METHOD_ADDED</code> Event (<code>paymentMethod.id</code>) or from the query parameter <code>PaymentMethodId</code> of Successful Payment notification </td></tr></tbody></table>

{% hint style="info" %}
Coda loads tax jurisdiction from the shopper who owns this payment method.
{% endhint %}

#### Sample Token Charge body <a href="#sample-token-charge-body" id="sample-token-charge-body"></a>

```json
{
  "country": 360,
  "currency": 360,
  "payType": 1005,
  "projectId": 1,
  "orderId": "1773302739024703",
  "offSessionType": "Unscheduled",
  "items": [
    {
      "code": "0a141558-3c61-45d0-b299-c867ec023543",
      "price": 1000,
      "name": "Monthly subscription"
    }
  ],
  "paymentMethodId": "pmt_sg1a9999ac56ae00000"
}
```

{% hint style="info" %}
Use `offSessionType` that binded with assigned `paymentMethodId` from a successful CIT-for-MIT save.
{% endhint %}

#### Immediate response <a href="#immediate-response" id="immediate-response"></a>

<table><thead><tr><th width="149.5958251953125">Field</th><th width="112.380615234375">Type</th><th>Description</th></tr></thead><tbody><tr><td><code>txnId</code></td><td>number</td><td>Coda transaction id. <code>0</code> if the request was rejected before a charge was created.</td></tr><tr><td><code>resultCode</code></td><td>integer</td><td><code>431</code> = accepted, processing. <code>0</code> = success (rare on this path). Other values = error.</td></tr><tr><td><code>resultDesc</code></td><td>string</td><td>English message on error. <code>null</code> when the charge is accepted for processing (<code>431</code>).</td></tr></tbody></table>

**Accepted (processing):**

```json
{
  "resultCode": 431,
  "resultDesc": null,
  "txnId": 7883459825339273000
}
```

{% hint style="warning" %}
Do not treat `431` as paid. Wait for the complete notification or Inquiry.
{% endhint %}

**Rejected example:**

```json
{
  "resultCode": 103,
  "resultDesc": "This transaction has been cancelled. No charges were made. (error:103).",
  "txnId": 0
}
```

***

### Authentication (Token Charge) <a href="#authentication-token-charge" id="authentication-token-charge"></a>

Token Charge endpoint uses **two** credentials:

<table><thead><tr><th width="198.225830078125">Credential</th><th>Role</th></tr></thead><tbody><tr><td><code>X-API-Key</code></td><td>Same string / key as Init Payment API <code>apiKey</code>.</td></tr><tr><td>HMAC signing secret</td><td>Separate secret Coda gives you out of band. Used only to sign the JWT. <strong>Never</strong> put it in <code>X-API-Key</code>, the JSON body, logs, or git.</td></tr></tbody></table>

There is no Coda endpoint that issues the JWT. Your server builds and signs it per request.

#### JWT header <a href="#jwt-header" id="jwt-header"></a>

```json
{ "alg": "HS256", "typ": "JWT", "kid": "n4bQgYhM" }
```

Algorithm is **HS256** only. Other algorithms are rejected.

Header `kid` is the **fingerprint** of the HMAC signing secret.

#### JWT claims <a href="#jwt-claims" id="jwt-claims"></a>

`exp - iat` must be **≤ 300 seconds**. Coda allows about 30 seconds of clock skew. Every listed claim is required. After signature verify, each claim must **exactly match** the JSON body (same types). Mismatch → `resultCode` **103**.

<table><thead><tr><th width="168.5732421875">Claim</th><th width="183.992919921875">Type</th><th>Rule</th></tr></thead><tbody><tr><td><code>iat</code></td><td>number</td><td>Unix seconds UTC. Must be ≤ now (with skew).</td></tr><tr><td><code>exp</code></td><td>number</td><td>Unix seconds UTC. Must be > now. <code>exp - iat</code> ≤ 300.</td></tr><tr><td><code>orderId</code></td><td>string</td><td>Match body <code>orderId</code>.</td></tr><tr><td><code>projectId</code></td><td>number</td><td>Match body <code>projectId</code>. Used with <code>country</code> to resolve the merchant.</td></tr><tr><td><code>country</code></td><td>number</td><td>Match body <code>country</code>.</td></tr><tr><td><code>currency</code></td><td>number</td><td>Match body <code>currency</code>.</td></tr><tr><td><code>payType</code></td><td>number</td><td>Match body <code>payType</code>.</td></tr><tr><td><code>offSessionType</code></td><td>string</td><td>Match body <code>offSessionType</code>.</td></tr><tr><td><code>items</code></td><td>array of <code>{code, price, name}</code></td><td>Same length and fields as body <code>items</code>. This binds the amount.</td></tr><tr><td><code>paymentMethodId</code></td><td>string</td><td>Match body <code>paymentMethodId</code>.</td></tr><tr><td><code>idempotencyKey</code></td><td>string</td><td>Match HTTP header <code>Idempotency-Key</code> </td></tr><tr><td>path</td><td>string</td><td>Match HTTP endpoint path </td></tr><tr><td>method</td><td>string</td><td>Match HTTP method</td></tr></tbody></table>

Do not include `iss`, `jti`, `kid`, `apiKey`, `userInitiated`, `shopper`, `profile`, or `paymentMethods` in the JWT. This endpoint is MIT-only; Coda sets customer-absent internally.

```json
{
  "iat": 1787126400,
  "exp": 1787126700,
  "idempotencyKey": "550e8400-e29b-41d4-a716-446655440000",
  "path": "/v1/payment/token/charge",
  "method": "POST",
  "orderId": "1773302739024703",
  "projectId": 1,
  "country": 360,
  "currency": 360,
  "payType": 1005,
  "offSessionType": "Unscheduled",
  "items": [
    {
      "code": "0a141558-3c61-45d0-b299-c867ec023543",
      "price": 1000,
      "name": "Monthly subscription"
    }
  ],
  "paymentMethodId": "pmt_sg1a9999ac56ae00000"
}
```

#### Sign (Node-style sketch) <a href="#sign-node-style-sketch" id="sign-node-style-sketch"></a>

```javascript
const jwt = require('jsonwebtoken');

const secret = process.env.CODA_TOKEN_CHARGE_HMAC;
const kid = process.env.CODA_TOKEN_CHARGE_KID;
const now = Math.floor(Date.now() / 1000);

const token = jwt.sign(
  {
    orderId: '1773302739024703',
    projectId: 1,
    country: 360,
    currency: 360,
    payType: 1005,
    offSessionType: 'Unscheduled',
    items: [{
      code: '0a141558-3c61-45d0-b299-c867ec023543',
      price: 1000,
      name: 'Monthly subscription'
    }],
    paymentMethodId: 'pm_1234567890000',
    // Must exactly match the Idempotency-Key HTTP header on the same request.
    idempotencyKey: '550e8400-e29b-41d4-a716-446655440000',
    path: "/v1/payment/token/charge",
    method: "POST",
    iat: now,
    exp: now + 300
  },
  secret,
  {
    algorithm: 'HS256',
    kid: kid
  }
);
```

***

### IP allowlist (Token Charge) <a href="#ip-allowlist-token-charge" id="ip-allowlist-token-charge"></a>

Coda only accepts Token Charge from **IPv4 addresses or CIDR ranges** you give your account manager (NAT / load-balancer egress of the **servers** that call the API — not developer laptops unless that is intentional for sandbox).

<table><thead><tr><th width="220.0653076171875">Rule</th><th>Detail</th></tr></thead><tbody><tr><td>Who sends IPs</td><td>You, before go-live</td></tr><tr><td>Format</td><td>Exact IPv4 and/or CIDR (e.g. <code>203.0.113.10</code>, <code>198.51.100.0/24</code>)</td></tr><tr><td>If missing or IP not listed</td><td>Token Charge is rejected</td></tr></tbody></table>

When your egress IPs change, notify Coda before cutover.


---

# Agent Instructions
This documentation is published with GitBook. GitBook is the documentation platform designed so that both humans and AI agents can read, navigate, and reason over technical content effectively. Learn more at gitbook.com.

## Querying This Documentation
If you need additional information that is not directly available in this page, you can query the documentation by asking a question.

Perform an HTTP GET request on the following URL with the `ask` and `goal` query parameters:

```
GET https://docs.coda.co/codapay/recurring-payments/submit-charge-with-tokenized-payment-method.md?ask=<question>&goal=<user_goal>
```

`ask` is the immediate question: it should be specific, self-contained, and written in natural language.
`goal` is what the user is ultimately trying to achieve, the reason they need the answer. Sharing it helps GitBook give you a better, more relevant answer. A goal is most helpful when it describes the outcome the user wants rather than restating the question. For example, with `ask=how do I create an API token`, a goal like `automate deployments from our CI pipeline` lets GitBook tailor the answer to that use case.

The response will contain a direct answer to the question and relevant excerpts and sources from the documentation.

Use this mechanism when the answer is not explicitly present in the current page, you need clarification or additional context, or you want to retrieve related documentation sections.
