Back to BlogCase Study

How we shipped a 1LINK 1BILL integration (and NADRA Verisys) for a government partner

A confidential government-sector engagement: a production 1LINK 1BILL integration shipped as a composer-installable Laravel package, alongside NADRA Verisys and a CI/CD pipeline that landed in under six months. The 1LINK package is now the foundation of our switch-integration consultancy.

February 22, 202610 min read

Most of our work is public. This one wasn't. Under NDA we joined a partner engineering team building a citizen-facing product for a federal government agency in Pakistan, and our job was to make the parts that scare everyone — the regulated rails — not scary.

The partner team owned the Laravel API, a React Native mobile app, and a React-based operator CMS. They knew their product cold. What they needed was a clean, testable, reusable way to talk to two regulated systems that don't take excuses: NADRA Verisys for citizen identity verification, and 1LINK 1BILL for bill payments. And they needed a delivery pipeline that wouldn't crumble the first time a government auditor asked how a release got to production. That is what we delivered.

The shape of the engagement

~5 mo

Kick-off to production

2

CodeFellow engineers

3

Composer packages shipped

0

Downtime at cutover

What was ours, what was theirs

The partner team was already mid-stride. They were building the Laravel API, React Native app, and React CMS in parallel and didn't need a second opinion on any of those — they needed missing pieces. So we scoped ourselves tightly:

  • Three Laravel composer packages: one for 1LINK 1BILL, one for NADRA Verisys, one for the agency's internal back-office system.
  • GitHub Actions pipelines for the API, the mobile app, the CMS, and our three packages — one consistent lifecycle across all seven repos.
  • Production deployment topology, secret management (including mTLS client certs), and the cutover playbook.
  • Standing code-review presence on the partner's PRs — light-touch, focused on the rail boundaries and the deploy path.

Two of us led the work: Junaid Ashraf as project manager — translating between the partner's PM, the government stakeholder, and us on every blocker that wasn't strictly code — and Ahmad Faryab Kokab as lead engineer, owning both the 1LINK and NADRA integrations end to end.

Three packages, one shape

A regulated integration is one of those places where the temptation is to wrap every request in cleverness — custom DSLs, baroque event systems, framework-of-the-month abstractions. We did the opposite. All three packages share the same shape and expose a small surface:

// NADRA Verisys — citizen verification, kept private
use Partner\Nadra\Facades\Nadra;

$result = Nadra::verify([
    'cnic'        => $request->cnic,
    'dob'         => $request->dob,
    'consent_ref' => $consentRef,   // who consented, when, scope
]);

if (!$result->matches()) {
    return ApiError::identityMismatch();
}

// 1LINK 1BILL — bill inquiry and payment, fully independent flow
use Partner\OneLink\Facades\OneLink;

$bill = OneLink::inquireBill($consumerNumber, $billerId);

$payment = OneLink::postPayment([
    'consumer_number' => $consumerNumber,
    'biller_id'       => $billerId,
    'amount'          => $bill->amountDue,
    'txn_id'          => $idempotencyKey,
]);

Each package owns its own concerns behind that surface: HMAC signing of every outbound request, idempotency keys persisted before the call (so retries are safe), structured logging scoped to the session, and a Pest test suite that replays recorded UAT cassettes — so CI doesn't depend on NADRA or 1LINK being up. The two rails stay independent: identity verification and bill payment are separate user journeys, separate audit trails, separate consent contexts. We resisted every temptation to chain them.

All three packages live on a private Packagist mirror. The partner team installs them like any other dependency. When NADRA or 1LINK changes a field — which they do — we ship a patch release, the partner runs composer update, their CI tests run, and they ship. No long-running branches, no merge windows.

Lessons

The five things that almost cost us a quarter

Every regulated integration has its sharp edges. These are the ones we paid for in time so the next team doesn't have to.

C

UAT availability is the schedule

NADRA and 1LINK UAT environments are shared infrastructure. We learned to record cassettes for every successful flow the same day we ran it — so when UAT was down, we weren't.

Idempotency before the network call

Persist the idempotency key before the outbound request, not after. A crash between 'request sent' and 'request logged' is the difference between one payment and two.

F

Settlement reconciliation is not optional

1LINK tells you about successful payments twice — once over the API, once in the next-day settlement file. If you don't reconcile, you will eventually disagree with the switch about money.

U

NADRA logs every call you make

Treat every Verisys call as auditable. Capture the consent reference, the operator, the timestamp, and the demographic match outcome — never the raw response payload. Data minimisation isn't a feature, it's the contract.

N

mTLS in CI is a Tuesday problem

Mutual TLS works fine in staging until your CI runner can't present a client certificate. Bake certificate handling into your test harness from day one, not the week before cutover.

The pipeline: one shape, every repository

The partner had seven repos in flight — the Laravel API, the React Native app, the React CMS, our three packages, and the operator CMS deploy repo. We gave them all the same pipeline shape: lint, type-check, unit, integration, security scan, build, deploy. Different commands per repo, identical lifecycle. New engineers stopped asking 'where does the deploy live?' on day two.

name: ci
on:
  push: { branches: [main] }
  pull_request: {}
  release: { types: [published] }

jobs:
  verify:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - uses: shivammathur/setup-php@v2
        with: { php-version: '8.3' }
      - run: composer install --no-progress
      - run: ./vendor/bin/pint --test
      - run: ./vendor/bin/phpstan analyse
      - run: ./vendor/bin/pest --coverage --min=85

  release:
    if: github.event_name == 'release'
    needs: verify
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - name: Publish to private packagist
        run: ./scripts/publish.sh ${{ github.event.release.tag_name }}

Deployments to UAT were on every merge to main. Deployments to production were tagged releases gated by a single approval. Secrets — including 1LINK mTLS client certs and the NADRA Verisys credentials — lived in Vault and were leased to runners just long enough to deploy. No secret ever existed at rest in a repo, in CI config, or on a developer laptop.

The cutover: boring on purpose

The morning of go-live, we did a single staged rollout. The first hour was test verifications and test transactions against a single biller. The next two hours were live citizen verifications and live production payments with a hard concurrency cap. By lunch we removed the cap. By close of business we'd processed thousands of CNIC verifications and bill payments, and the next morning's 1LINK settlement file matched our ledger row-for-row. Zero downtime, zero reversals, zero pages.

That's not because we got lucky. It's because the partner team had two months of UAT reps behind them, the three packages had test suites that exercised every flow including the unhappy ones, and the runbook had the exact rollback step printed at the top.

What the team said

From the people who shipped it

The three packages made the rails feel like ordinary dependencies. We never had to think about HMAC, retries, or NADRA's audit format — we called the facade and tested the happy and unhappy paths.

PT

Partner team lead

Engineering Lead · Confidential, software partner

In a government engagement, what matters most is that someone is translating friction into tickets we can actually solve, instead of escalations. That was the job. We held the seam.

JA

Junaid Ashraf

Project Manager · CodeFellow

NADRA and 1LINK punish sloppy code in opposite ways — NADRA in the audit, 1LINK in the settlement file. Keeping the rails independent and giving each its own package was what let us sleep through cutover night.

AF

Ahmad Faryab Kokab

Lead Engineer · CodeFellow

Stuck on a 1LINK1BILL integration?

We've taken it to production. The 1LINK package and Junaid's switch consultancy are how we help teams already mid-integration ship without a rewrite.

NDAs welcome. First call is on us.

Back to Blog