Start here

Quickstart

From nothing to a paid test payment, in six steps.

This is the whole integration, in the order it is done. It ends with a real (test) payment landing in the dashboard feed. Budget twenty minutes, most of it waiting for a provider dashboard to load.

  1. 1

    Connect a provider in test mode

    In the dashboard open Providers → Add a provider, choose Paystack, Flutterwave or OPay, pick Test keys, and paste the test public and secret keys from that provider's own dashboard (Paystack: Settings → API Keys & Webhooks. Flutterwave: Settings → API Keys. OPay: API Keys & Webhooks, where OPay also shows the merchant ID Harbour asks for). Harbour validates them with the provider before it stores anything.

    Harbour then shows a webhook URL to paste back into the provider's dashboard, and for Flutterwave a secret hash shown once. Do that now: it is how Harbour hears that a payment succeeded.

  2. 2

    Create your test keys

    Open Settings → Developers and create a key pair in test mode. You get:

    • hb_sk_test_… — the secret key, shown once. Server only.
    • hb_pk_test_… — the public key, safe in a browser.

    On the same screen, add a webhook endpoint pointing at your server and copy its whsec_… signing secret, also shown once.

  3. 3

    Install Harbour Connect

    Terminal
    composer require nordaxiz/harbour-connect:^0.1

    PHP 8.1 or newer, with ext-curl and ext-json. No other dependencies. The SDK is below 1.0.0, so ^0.1 pins it to patches while the API settles (why). Put the keys in your environment, never in code:

    .env
    HARBOUR_SECRET_KEY=hb_sk_test_your_key
    HARBOUR_WEBHOOK_SECRET=whsec_your_endpoint_secret
  4. 4

    Send the customer to checkout

    Create the charge on your server, store the Harbour reference against the order, then redirect.

    PHP
    SDK language
    • PHP
    • Node.jsComing soon
    • PythonComing soon
    • JavaComing soon
    • GoComing soon
    • .NETComing soon
    Any language can call the API today
    checkout.php
    <?php
    
    use Nordaxiz\HarbourConnect\Harbour;
    
    Harbour::configure((string) getenv('HARBOUR_SECRET_KEY'));
    
    $checkout = Harbour::charge(12_000_00, 'funke@example.ng', [
        'reference'   => 'order-1043',                       // your own order reference
        'description' => 'Aso-ebi order, 12 yards',
        'return_url'  => 'https://shop.example.ng/thanks',
    ]);
    
    // store $checkout->reference ("HB-88413") against the order before you redirect
    $order->harbour_reference = $checkout->reference;
    $order->save();
    
    header('Location: ' . $checkout->checkoutUrl, true, 303);

    Amounts are integer kobo: 12_000_00 is ₦12,000. Floats are refused.

  5. 5

    Confirm the payment

    The customer comes back to your return_url with ?reference=HB-88413&status=succeeded. Ignore that status and ask Harbour:

    PHP
    SDK language
    • PHP
    • Node.jsComing soon
    • PythonComing soon
    • JavaComing soon
    • GoComing soon
    • .NETComing soon
    Any language can call the API today
    thanks.php
    <?php
    
    use Nordaxiz\HarbourConnect\Harbour;
    
    $checkout = Harbour::verify($_GET['reference'] ?? '');   // GET /v1/charges/HB-88413
    
    if ($checkout->isPaid()
        && $checkout->merchantReference === $order->reference
        && $checkout->amountKobo === $order->total_kobo) {
        $order->markPaid();                                  // idempotent: the webhook may get here first
    }

    Check the amount and your own reference before you fulfil anything.

  6. 6

    Add the webhook

    A customer who closes the tab never reaches your return URL, but the webhook still arrives. This, not the redirect, is what makes fulfilment reliable.

    PHP
    SDK language
    • PHP
    • Node.jsComing soon
    • PythonComing soon
    • JavaComing soon
    • GoComing soon
    • .NETComing soon
    Any language can call the API today
    webhook.php
    <?php
    
    use Nordaxiz\HarbourConnect\Exception\SignatureVerificationException;
    use Nordaxiz\HarbourConnect\Harbour;
    use Nordaxiz\HarbourConnect\Webhook\Event;
    
    try {
        Harbour::webhooks((string) getenv('HARBOUR_WEBHOOK_SECRET'))->handle(null, function (Event $event): void {
            if ($event->type !== Event::CHARGE_SUCCEEDED) {
                return;
            }
            $transaction = $event->transaction();
            $order = Order::findByReference($transaction->merchantReference);
    
            if ($order && $order->total_kobo === $transaction->amountKobo) {
                $order->markPaid($event->id);                // de-duplicate on the event id
            }
        });
        http_response_code(200);
    } catch (SignatureVerificationException $e) {
        http_response_code(400);
    }

    Answer 2xx within ten seconds and do slow work in a queue. Harbour retries a failed delivery for 24 hours. The full rules are on Webhooks.

What you have now

Pay for a test order with the provider's test card. Within a second or two:

  • your webhook fires and the order is marked paid;
  • the payment appears in the dashboard feed with its provider, channel and fee;
  • Harbour::verify() and GET /v1/transactions both show it.

Nothing about this changes when you go live except the keys. Read Testing for the checks worth automating, then Going live for the switch.