SDKs

Node.js SDK

The official Credicorp client for Node.js and TypeScript. It handles OAuth 2.0 token caching, automatic retries with idempotency, cursor pagination and webhook signature verification — so you write business logic, not plumbing.

Requirements. Node.js 18 LTS or newer (native fetch and crypto.webcrypto). Ships first-class TypeScript types — no @types package needed. Both ESM and CommonJS builds are included.

Install

Add the package from npm. The SDK has zero runtime dependencies — everything is built on the platform standard library, so it stays small in your container image.

bash
npm install @credicorp/sdk
# or: pnpm add @credicorp/sdk · yarn add @credicorp/sdk

Configure from the environment

Never hard-code secrets. Read your key from the environment — the client selects sandbox or live automatically from the key prefix (sk_test_… vs sk_live_…), so the same code runs in both without a branch.

.env
# .env — keep out of version control
CC_SECRET_KEY=sk_test_9fK2c…
CC_WEBHOOK_SECRET=whsec_4Td1…
typescript
import { Credicorp } from '@credicorp/sdk';

const cc = new Credicorp(process.env.CC_SECRET_KEY!, {
  // all optional — sensible defaults shown
  maxRetries: 3,        // retries 429 / 5xx with backoff
  timeoutMs:  30_000,
});

The constructor also accepts { apiBase } to pin a host, and an OAuth clientId/clientSecret pair instead of a secret key when you call the partner plane on behalf of multiple merchants. Tokens are minted lazily, cached in memory and refreshed before they expire — you never touch /oauth/token directly.

Constructor options

OptionTypeDescription
maxRetriesnumberoptRetries on 429 and 5xx using exponential backoff with jitter; honours Retry-After. Default 3.
timeoutMsnumberoptPer-request timeout in milliseconds. Default 30000.
apiBasestringoptOverride the API host. Defaults to https://api.credicorp.co.uk.
fetchtypeof fetchoptInject a custom fetch (instrumented or proxied) for tracing and tests.

Your first call — create an application

This creates a business-loan application for a UK incorporated company and returns a handoffUrl you redirect the applicant to. Amounts are always in pence to avoid floating-point rounding. The SDK attaches an Idempotency-Key automatically when you don't supply one, but passing your own (an order id, say) makes retries provably safe.

typescript
const application = await cc.applications.create({
  business:    { companyNumber: '16093826' },
  amountPence: 2_500_000,          // £25,000
  termMonths:  12,
  purpose:     'working_capital',
  redirectUri: 'https://yourapp.com/return',
}, { idempotencyKey: 'order_88421' });

console.log(application.id);          // 'app_8Kd2c9Qm'
console.log(application.handoffUrl);  // hosted apply journey

That is exactly one authenticated HTTP request. The equivalent in raw curl:

bash
curl -s https://api.credicorp.co.uk/partner/v1/applications \
  -H "Authorization: Bearer $TOKEN" \
  -H "Idempotency-Key: order_88421" \
  -H "Content-Type: application/json" \
  -d '{ "business": { "company_number": "16093826" },
       "amount_pence": 2500000, "term_months": 12,
       "purpose": "working_capital",
       "redirect_uri": "https://yourapp.com/return" }'

The body the SDK decodes into a typed Application:

201 Created
{
  "id": "app_8Kd2c9Qm",
  "status": "created",
  "amount_pence": 2500000,
  "term_months": 12,
  "handoff_url": "https://apply.credicorp.co.uk/s/3fa7…",
  "created_at": "2026-06-29T10:14:02Z"
}

From here you read the decision (poll cc.applications.decision(id) or, better, react to the decision.completed webhook) and, on approval, draw down funds over the Payments API.

Pagination

List endpoints are cursor-paginated. Rather than juggle has_more and the cursor token yourself, the SDK exposes an async iterator that fetches each page lazily as you consume it. Walk an entire result set with a plain for await … of loop:

typescript
// Walks every page automatically — one network call per page.
for await (const app of cc.applications.list({
  status:       'funded',
  createdAfter: '2026-06-01',
  limit:        50,            // page size, max 100
})) {
  console.log(app.id, app.amountPence);
}

Need raw control — for a "load more" button, or to checkpoint a long backfill? Request a single page and read the cursor yourself:

typescript
const page = await cc.applications.list({ limit: 25 }).page();

page.data;        // Application[]
page.hasMore;     // boolean
page.nextCursor;  // pass back as { cursor } next call

if (page.hasMore) {
  const next = await cc.applications
    .list({ limit: 25, cursor: page.nextCursor }).page();
}

Cursors are opaque and stable — safe to persist and resume from later. Don't construct or parse them. See the Pagination reference for the full contract.

Webhooks

Webhooks are how you react to decisions, disbursements and repayments without polling. Every delivery carries a Credicorp-Signature header. Always verify it before trusting the body — an unverified endpoint is a forgery waiting to happen. The SDK's webhooks.constructEvent() checks the HMAC, rejects stale timestamps (replay protection) and returns a typed event.

The signature is computed over the raw request body, so hand the verifier the unparsed bytes — mount express.raw() on the webhook route, not express.json():

typescript
import express from 'express';
import { Credicorp } from '@credicorp/sdk';

const cc  = new Credicorp(process.env.CC_SECRET_KEY!);
const app = express();

app.post('/webhooks/credicorp',
  express.raw({ type: 'application/json' }),   // raw body required
  (req, res) => {
    let event;
    try {
      event = cc.webhooks.constructEvent(
        req.body,                            // Buffer
        req.header('Credicorp-Signature')!,
        process.env.CC_WEBHOOK_SECRET!,
      );
    } catch {
      return res.status(400).send('invalid signature');
    }

    switch (event.type) {
      case 'decision.completed':
        markDecision(event.data.applicationId, event.data.outcome);
        break;
      case 'payment.settled':
        reconcile(event.data.paymentId);
        break;
    }

    res.json({ received: true });   // 2xx fast, then process async
  });

The header itself looks like the line below — a timestamp and a hex HMAC-SHA256 over {timestamp}.{body}, keyed by your whsec_… secret:

http
Credicorp-Signature: t=1719660000,v1=4f8b9c1d…e02a

Common event types you'll handle:

EventFires when
application.updatedStatus moves through the lifecycle (created → submitted → in_review).
decision.completedThe AI decision engine returns an outcome — approved, referred or declined.
payment.settledA PISP disbursement or a repayment clears via Faster Payments / Open Banking.
account.updatedA live facility's balance, arrears state or repayment schedule changes.

Respond 2xx within 10 seconds. Acknowledge first, then do slow work on a queue. Anything else is retried with backoff, and deliveries are at-least-once — key your handlers on event.id so a redelivery is a no-op.

Errors

Failed requests throw a typed CredicorpError carrying the HTTP status, the machine-readable code and the request id you'll quote to support. Validation failures itemise the offending fields.

typescript
import { CredicorpError } from '@credicorp/sdk';

try {
  await cc.applications.create({ /* … */ });
} catch (err) {
  if (err instanceof CredicorpError) {
    console.error(err.status);     // 422
    console.error(err.code);       // 'validation_error'
    console.error(err.requestId);  // 'req_1a2b3c' — quote to support
    console.error(err.fields);     // [{ field: 'amount_pence', … }]
  }
}
StatuscodeMeaning
401unauthorizedMissing or expired credentials. The SDK refreshes tokens for you; a persistent 401 means a bad key.
422validation_errorThe request body failed validation. Inspect err.fields.
429rate_limitedToo many requests — retried automatically up to maxRetries.
503service_unavailableTransient upstream issue — safe to retry; the SDK already does.

Going further

  • Every method maps 1:1 to the API referencecc.payments, cc.identity, cc.accounts, cc.webhooks.
  • Run end-to-end against the sandbox with deterministic test companies before requesting live access.
  • New to the model? Start with Core concepts, then the Integrate lending guide.