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.
Before you start: the merchant account
Section titled “Before you start: the merchant account”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:
- 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.
- 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.
1. Credentials
Section titled “1. Credentials”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.
2. Webhook URL
Section titled “2. Webhook URL”In the CCBill Admin, add a webhook to the sub-account:
- URL:
https://api.kasskeep.com/v1/stores/ccbill/<app_id>/webhook(the app’sccbill_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 asunrecognizedand ignored.
CCBill webhooks are not signed. For every request Kasskeep:
- 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 yourwebhook_ips) and returns403 ip_not_allowedotherwise; - checks that
clientAccnumandclientSubaccare yours (400 account_mismatch); - 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_notificationand records nothing; if Data Link is down it returns502so the delivery can be retried; - deduplicates by event type, subscription id, transaction id and timestamp;
- ignores sales whose
X-kasskeep_app_idis 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.
3. Map products
Section titled “3. Map products”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:
# subscription: 9.99 USD every 30 days until cancellednpx @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¤cyCode=840"# one-time: 4.99 USD, 2 days of access on CCBill's sidenpx @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¤cyCode=840"# price set on the form itselfnpx @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),
numRebillsis 1 to 99 (99 means until cancelled). currencyCodeis 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.
4. Start a checkout
Section titled “4. Start a checkout”POST /v1/checkoutX-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_urlandcancel_urlare not sent to CCBill. quantitymust be 1.- Returning from the form does not prove payment; grant access from the webhook or
GET /v1/customers.
Recording and events
Section titled “Recording and events”original_transaction_idis the CCBill subscription id; each charge is a transaction keyed by its CCBill transaction id.price_microsandcurrencyare 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 |
Refunds
Section titled “Refunds”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.
Reconciliation
Section titled “Reconciliation”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.
Test mode
Section titled “Test mode”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":
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.