Skip to content

CCBill

The CCBill channel sells to the web version of your app where the content is not allowed on general processors. Customers pay on a CCBill-hosted FlexForm and the money goes to your own CCBill merchant account. Kasskeep builds the payment link, checks every webhook, records the result and sends events. Channel id: ccbill, platform: web, data.store: "ccbill".

Capabilities: subscriptions, one-time purchases, refunds, refund lookup, active refunds, chargebacks, reconciliation and test mode.

You need your own CCBill merchant account; Kasskeep does not process payments. CCBill reviews every site before activation, so prepare:

  • Company details and the identity documents of the owners. The application must match the documents exactly.
  • A live site with an age gate (18+), terms of service, a privacy policy, a refund and cancellation policy, a customer support contact, and an accurate 18 U.S.C. 2257 statement where it applies.
  • Clear disclosure of trial and recurring prices before the payment page; CCBill’s name, logo and support link must stay visible on its payment page.
  • Content within CCBill’s Merchant Acceptable Use Policy.

CCBill publishes the details in its merchant FAQ and acceptable use policy. Once approved you get a 6-digit client account number and one or more 4-digit sub-accounts.

Ask CCBill Merchant Support for two things:

  1. Dynamic pricing on the sub-account, so Kasskeep can set the price in the link. Then copy the sub-account encryption key (salt) from the CCBill Admin.
  2. A Data Link user (username and password) for the Subscription Management API. Data Link only answers registered addresses; give CCBill the outbound IP addresses of your Kasskeep service.
Terminal window
curl -X PUT https://api.kasskeep.com/v1/admin/tenants/ten_xxx/apps/app_xxx/credentials/ccbill \
-H "Authorization: Bearer kkp_..." -H "Content-Type: application/json" \
-d '{"values": {"client_accnum": "900100", "client_subacc": "0000", "salt": "...",
"datalink_username": "...", "datalink_password": "..."}}'
Key Required Meaning
client_accnum yes 6-digit client account number
client_subacc yes 4-digit sub-account the FlexForms belong to
salt for dynamic prices Sub-account encryption key, used for formDigest
datalink_username, datalink_password yes Data Link user, used to confirm webhooks, refund and reconcile
webhook_ips no Allowed webhook senders, comma-separated IPs or IPv4 CIDRs; the word ccbill stands for CCBill’s published ranges (the default)
environment no production (default) or sandbox for a CCBill test account

Values are encrypted with the tenant key and never returned; the console shows only “configured · last 4”. POST .../credentials/ccbill/verify calls Data Link once to check the user.

In the CCBill Admin, add a webhook to the sub-account:

  • URL: https://api.kasskeep.com/v1/stores/ccbill/<app_id>/webhook (the app’s ccbill_webhook_path).
  • Format: JSON or URL-encoded; both are accepted.
  • Events: NewSaleSuccess, NewSaleFailure, RenewalSuccess, RenewalFailure, Cancellation, Expiration, Refund, Return, Void, Chargeback. Other events are stored as unrecognized and ignored.

CCBill webhooks are not signed. For every request Kasskeep:

  1. checks the sender address against CCBill’s published ranges 64.38.212.0/24, 64.38.215.0/24, 64.38.240.0/24, 64.38.241.0/24 (or your webhook_ips) and returns 403 ip_not_allowed otherwise;
  2. checks that clientAccnum and clientSubacc are yours (400 account_mismatch);
  3. asks the Data Link Subscription Management API whether the subscription exists on your account and whether it shows the rebill, refund, void, chargeback, cancellation or expiry the webhook claims. If not, it returns 400 unverified_notification and records nothing; if Data Link is down it returns 502 so the delivery can be retried;
  4. deduplicates by event type, subscription id, transaction id and timestamp;
  5. ignores sales whose X-kasskeep_app_id is a different app, and takes the product from the price that was actually paid, not from the pass-through field.

A forged webhook therefore needs a sender inside CCBill’s network and a matching record on your CCBill account. That is weaker than a signature (it relies on the network path and on Data Link answering correctly) but it is CCBill’s recommended setup. Failures raise the store.signature_invalid alert.

Map each product id to a FlexForm and its price. The external id is the FlexForm id, optionally followed by CCBill’s dynamic pricing parameters:

Terminal window
# subscription: 9.99 USD every 30 days until cancelled
npx @kasskeep/cli products set --id app_xxx --channel ccbill --product-id pro_monthly --type subscription \
--external-id "687fa3e0-e60d-4466-88e2-181fa56dd6a9?initialPrice=9.99&initialPeriod=30&recurringPrice=9.99&recurringPeriod=30&numRebills=99&currencyCode=840"
# one-time: 4.99 USD, 2 days of access on CCBill's side
npx @kasskeep/cli products set --id app_xxx --channel ccbill --product-id gems_100 --type one_time \
--external-id "687fa3e0-e60d-4466-88e2-181fa56dd6a9?initialPrice=4.99&initialPeriod=2&currencyCode=840"
# price set on the form itself
npx @kasskeep/cli products set --id app_xxx --channel ccbill --product-id vip --type subscription --external-id "cb617dcc-8467-49ab-b3a7-735ce1d60ad9"
  • Prices have two decimals, periods are days (1 to 365), numRebills is 1 to 99 (99 means until cancelled).
  • currencyCode is the ISO numeric code: 840 USD, 978 EUR, 826 GBP, 124 CAD, 036 AUD, 392 JPY.
  • Subscriptions need all three recurring parameters; one-time products must not have them.
  • In the FlexForm settings, allow dynamic prices to be passed to the form.
POST /v1/checkout
X-Kasskeep-Key: pk_...
{"app_user_id": "6f1c...", "channel": "ccbill", "product_id": "pro_monthly",
"success_url": "https://play.example.com/done", "cancel_url": "https://play.example.com/shop"}

The response is {"url": "https://api.ccbill.com/wap-frontflex/flexforms/<flex id>?...", "session_id": "ccbo_..."}. The link carries clientSubacc, the price, formDigest (MD5 of the price parameters and the salt) and the pass-through fields X-kasskeep_app_id, X-kasskeep_app_user_id, X-kasskeep_product_id and X-kasskeep_order_id (the session_id).

  • After payment CCBill sends the customer to the approval or denial URL set on the FlexForm; success_url and cancel_url are not sent to CCBill.
  • quantity must be 1.
  • Returning from the form does not prove payment; grant access from the webhook or GET /v1/customers.
  • original_transaction_id is the CCBill subscription id; each charge is a transaction keyed by its CCBill transaction id.
  • price_micros and currency are what the customer was billed. The amount paid to you in USD (accountingAmount) is stored separately for revenue reports.
CCBill webhook Event
NewSaleSuccess purchase (one-time) or subscription.started
NewSaleFailure Recorded only
RenewalSuccess subscription.renewed; data.recovered after a failed rebill
RenewalFailure subscription.billing_issue; grace period until nextRetryDate
Cancellation subscription.canceled (access lasts to the end of the period)
Expiration subscription.expired
Refund, Return refund; a partial amount only notifies (data.partial: true), a full one revokes
Void revoked with data.revocation: "void"
Chargeback chargeback (data.final: true) and refund with data.reason: "chargeback"; access is revoked
Terminal window
curl -X POST https://api.kasskeep.com/v1/admin/tenants/ten_xxx/apps/app_xxx/channels/ccbill/refunds \
-H "Authorization: Bearer kkp_..." -H "Content-Type: application/json" \
-d '{"transaction_id": "0912191101000000159", "amount_micros": 5000000}'

Owners and admins only (refunds.write). Kasskeep calls Data Link refundTransaction; leave out amount_micros for a full refund. CCBill refunds the latest charge of the subscription and also cancels it, so older charges return 409 refund_not_latest; refund those in the CCBill Admin. The refund is recorded when CCBill’s Refund webhook arrives.

Once a day Kasskeep asks Data Link for the status of every live CCBill subscription and recent one-time purchase (up to 200 per app). A missed expiry or cancellation is applied and sends its event; more rebills, refunds, voids or chargebacks at CCBill than Kasskeep recorded are reported as a reconcile.failed alert with the subscription ids.

With the test store on, you can replay CCBill notifications without a CCBill account. They run through the same recording and events with environment: "test":

Terminal window
curl -X POST https://api.kasskeep.com/v1/admin/tenants/ten_xxx/apps/app_xxx/channels/ccbill/simulate \
-H "Authorization: Bearer kkp_..." -H "Content-Type: application/json" \
-d '{"event_type": "NewSaleSuccess", "app_user_id": "6f1c...", "product_id": "pro_monthly"}'

event_type is any of the events above. Later events take subscription_id or app_user_id (the user’s latest simulated purchase), and refunds can take amount_micros. A refund request for a simulated transaction replays a Refund instead of calling CCBill.