Skip to main content

Payments

Take UPI payments inside a WhatsApp chat: create and manage payment configurations on a WABA, then send order details and order status messages.

WhatsApp Payments lets a customer pay an itemised bill from the chat itself with any UPI app. Two pieces: a payment configuration on the WABA that says where the money lands, and the two order messages that bill the customer and then settle the order.

All endpoints are under https://api.callmissed.com/api/v1/whatsapp.

India and UPI only. These endpoints implement the India flow, with payment_type: "upi" and INR. Any other payment_type on a send returns 501, because other regions use a different request shape rather than a variation of this one.

How it fits together

  1. 1

    Configure

    registers a UPI VPA or a payment gateway on the WABA

  2. 2

    Link

    For a gateway, the merchant opens the returned to finish linking. A VPA is usable immediately

  3. 3

    Bill

    sends the itemised bill. The customer pays in their UPI app

  4. 4

    Settle

    moves the order off "Order pending"

Choosing the WABA

A payment configuration belongs to a WhatsApp Business Account, not to a phone number, so these endpoints take the same account selector as templates. Supply exactly one:

FieldTypeWhere it comes from
account_idUUIDThe id from GET /accounts
waba_idstring, max 64Meta's WABA id

The account is always resolved against your workspace, so naming a WABA you do not own returns the same 404 as one that does not exist.

The two order sends are per-number instead, and take phone_id or phone_number_id like every other send.

Providers

provider_name picks how the money is collected.

provider_nameWhat it isReady when
upi_vpaA UPI VPA handle you own, linked directlyImmediately
razorpayPayment gatewayAfter the merchant completes the OAuth link
payuPayment gatewayAfter the merchant completes the OAuth link
zaakpayPayment gatewayAfter the merchant completes the OAuth link

A gateway configuration exists as soon as you create it but cannot take a payment until the merchant visits the oauth_url the create returns. Until then, an order details message quoting it will not be payable.

Create a payment configuration

POST /api/v1/whatsapp/payment_configurations · scope whatsapp:write

FieldTypeRequiredNotes
account_id / waba_idUUID / stringOne ofThe WABA to configure
configuration_namestring, 1 to 60 charsYesThe name you quote as payment_configuration when sending an order
provider_namestring, 1 to 32 charsYesOne of the providers above
merchant_vpastring, max 256For upi_vpaThe VPA handle to collect into
merchant_category_codestring, max 32NoYour MCC
purpose_codestring, max 32NoPurpose code, where your provider requires one
redirect_urlstring, max 2048NoWhere to send the merchant after they finish the OAuth link
curl -X POST https://api.callmissed.com/api/v1/whatsapp/payment_configurations \
  -H "Authorization: Bearer cm_your_api_key" \
  -H "Content-Type: application/json" \
  -d '{
    "waba_id": "102290129340398",
    "configuration_name": "acme-upi",
    "provider_name": "upi_vpa",
    "merchant_vpa": "acmecoffee@okhdfcbank"
  }'

Response (200 OK)

{
  "account_id": "1a2b3c4d-5e6f-7a8b-9c0d-1e2f3a4b5c6d",
  "waba_id": "102290129340398",
  "configuration_name": "acme-razorpay",
  "success": true,
  "oauth_url": "https://business.example.com/payments/link?token=...",
  "expiration": 1776000000
}
FieldTypeNotes
account_idUUIDThe WABA's CallMissed id
waba_idstringMeta's WABA id
configuration_namestringEchoes the name you created
successbooleanWhether the configuration was created
oauth_urlstring, nullablePresent for a gateway provider only. The merchant must visit it to finish linking
expirationinteger, nullableWhen that link stops working

List payment configurations

GET /api/v1/whatsapp/payment_configurations · scope whatsapp:read

Read live, with no cached fallback, so you never see a status we stored earlier and never refreshed.

Query paramTypeRequiredNotes
account_id / waba_idUUID / stringOne ofThe WABA to read
curl "https://api.callmissed.com/api/v1/whatsapp/payment_configurations?waba_id=102290129340398" \
  -H "Authorization: Bearer cm_your_api_key"

Response (200 OK)

{
  "account_id": "1a2b3c4d-5e6f-7a8b-9c0d-1e2f3a4b5c6d",
  "waba_id": "102290129340398",
  "payment_configurations": [
    {
      "configuration_name": "acme-upi",
      "status": "Active",
      "provider_name": "upi_vpa",
      "provider_mid": null,
      "merchant_vpa": "acmecoffee@okhdfcbank",
      "merchant_category_code": { "code": "5814", "description": "Restaurants" },
      "purpose_code": null,
      "created_timestamp": 1774000000,
      "updated_timestamp": 1774000000
    }
  ]
}

The payment configuration object

FieldTypeNotes
configuration_namestringThe name you quote when sending an order
statusstring, nullableActive, Needs_Connecting or Needs_Testing. Only Active can take a payment
provider_namestring, nullableThe provider it was created with
provider_midstring, nullableThe gateway's merchant id, where the provider issues one
merchant_vpastring, nullableThe VPA handle, for a upi_vpa configuration
merchant_category_codestring or object, nullableReported either as a plain code or as { code, description }
purpose_codestring or object, nullableSame, when set
created_timestamp / updated_timestampinteger, nullableEpoch seconds

Fields are broadly optional because the read endpoints and the status webhook each report a different subset.

Get one payment configuration

GET /api/v1/whatsapp/payment_configurations/{configuration_name} · scope whatsapp:read

Query paramTypeRequiredNotes
account_id / waba_idUUID / stringOne ofThe WABA to read
curl "https://api.callmissed.com/api/v1/whatsapp/payment_configurations/acme-upi?waba_id=102290129340398" \
  -H "Authorization: Bearer cm_your_api_key"

Returns a single payment configuration object, or 404 when no configuration on that WABA carries the name.

Poll this after creating a gateway configuration to see it move to Active once the merchant has finished linking.

POST /api/v1/whatsapp/payment_configurations/{configuration_name}/oauth_link · scope whatsapp:write

The link a gateway create returns expires. This is how a merchant who never finished linking, or whose link went stale, gets a fresh one without recreating the configuration.

FieldTypeRequiredNotes
account_id / waba_idUUID / stringOne ofThe WABA
redirect_urlstringNoWhere to send the merchant afterwards
curl -X POST https://api.callmissed.com/api/v1/whatsapp/payment_configurations/acme-razorpay/oauth_link \
  -H "Authorization: Bearer cm_your_api_key" \
  -H "Content-Type: application/json" \
  -d '{
    "waba_id": "102290129340398",
    "redirect_url": "https://acme.example.com/payments/linked"
  }'

Response (200 OK)

{
  "account_id": "1a2b3c4d-5e6f-7a8b-9c0d-1e2f3a4b5c6d",
  "waba_id": "102290129340398",
  "configuration_name": "acme-razorpay",
  "oauth_url": "https://business.example.com/payments/link?token=...",
  "expiration": 1776000000
}

Only meaningful for a gateway provider. A upi_vpa configuration has nothing to link.

Delete a payment configuration

DELETE /api/v1/whatsapp/payment_configurations/{configuration_name} · scope whatsapp:write

Query paramTypeRequiredNotes
account_id / waba_idUUID / stringOne ofThe WABA
curl -X DELETE "https://api.callmissed.com/api/v1/whatsapp/payment_configurations/acme-razorpay?waba_id=102290129340398" \
  -H "Authorization: Bearer cm_your_api_key"

Response (200 OK)

{
  "account_id": "1a2b3c4d-5e6f-7a8b-9c0d-1e2f3a4b5c6d",
  "waba_id": "102290129340398",
  "configuration_name": "acme-razorpay",
  "success": true
}

Stop sending first. Make sure no new order messages quote this configuration before you unlink it, or those bills will have nowhere to collect into.

Billing a customer

The two sends live with the rest of the send reference:

Both need the whatsapp:send scope and both are window-limited like any other free-form send. Tie the two together with reference_id: it is unique per order details message, and quoting it on an order status update is what moves that specific order off "Order pending".