Abia State Government Services Portal
Home · Developers

Webhooks

Subscribe once and the platform tells you when a gate moves, a payment lands, a document issues or a voucher changes state — signed, retried, and never delivered to a private address.

Subscribe

One call registers an endpoint and returns the signing secret — once. Store it before you close the response.

curl -s https://services.abiastate.gov.ng/api/v1/webhooks \
  -H 'Authorization: Bearer ak_sbx_4K21PQ7M.0tS3-…' \
  -H 'Idempotency-Key: hooks-2026-09-14' \
  -H 'Content-Type: application/json' \
  -d '{
        "url": "https://lands.example.gov.ng/hooks/abia",
        "events": ["transaction.gate_changed", "document.issued"]
      }'

The URL must be https, on a public host, and must not redirect. A loopback address, a private range, a link-local address or a name that resolves to one is refused outright — a webhook that can be pointed at the inside of a network is a server-side request forgery with a nice name.

What a delivery looks like

POST /hooks/abia HTTP/1.1
Host: lands.example.gov.ng
Content-Type: application/json
User-Agent: ABSG-Webhooks/1.0
X-Abia-Event: document.issued
X-Abia-Event-Id: EVT-2026-9K41XA
X-Abia-Delivery-Id: 4417
X-Abia-Signature: t=1789123964,v1=9c1b…

{
  "id": "EVT-2026-9K41XA",
  "event": "document.issued",
  "created_at": "2026-09-14T10:02:11+01:00",
  "env": "sandbox",
  "mda": "20008001",
  "data": {
    "document_number": "AB/LND/COFO/2026/4F2K7C/TCC-2026-004418",
    "short_number": "AB-LND26-4F2K7C",
    "transaction_ref": "BTX-2026-004871",
    "tcc_ref": "TCC/2026/004418",
    "issued_at": "2026-09-14T10:02:11+01:00",
    "verification_id": "VRF-2026-9F3C1D7A4B2E"
  }
}
  • Answer 2xx as soon as you have stored it. Anything else is a failure and will be retried.
  • Answer within 10 seconds. Do the work afterwards, not before you reply.
  • At least once, not exactly once. Deduplicate on X-Abia-Delivery-Id — one delivery to one endpoint. X-Abia-Event-Id, which is the body's id, names the event itself, and several endpoints may each be told about it.
  • Order is not guaranteed. Treat the payload as the current fact and ignore an event older than what you hold.
  • A webhook is a nudge, not a record. Confirm anything that matters by reading the endpoint it names.
  • The fields under data differ by event and are additive. Read the ones you need and ignore the rest; a client that rejects an unknown field breaks on an improvement.

Verify the signature

The signature covers the timestamp and the exact body bytes. Compare in constant time, and reject a timestamp far from your own clock — otherwise a captured delivery can be replayed at leisure.

Verifying in PHP
<?php
$raw    = file_get_contents('php://input');
$header = $_SERVER['HTTP_X_ABIA_SIGNATURE'] ?? '';   // t=…,v1=…

parse_str(str_replace(',', '&', $header), $parts);
$ts  = (int)($parts['t'] ?? 0);
$sig = (string)($parts['v1'] ?? '');

if (abs(time() - $ts) > 300) {
    http_response_code(400);          // stale: refuse, do not process
    exit;
}
$expected = hash_hmac('sha256', $ts . '.' . $raw, $secret);
if (!hash_equals($expected, $sig)) {
    http_response_code(401);
    exit;
}

http_response_code(200);              // acknowledge FIRST, then do the work

Retries

A failed delivery is retried on a widening schedule, up to 8 attempts, and then marked dead.

AttemptWhen
1Immediately.
21 minute later.
35 minutes.
415 minutes.
51 hour.
66 hours.
7 and 824 hours apart, then the delivery is dead and will not be sent again.

Fifty consecutive failures pause the subscription and notify BIR. Fix the endpoint, then resume it yourself with POST /webhooks/{id}/enable — the platform will not quietly keep hammering a host that has stopped answering. Deliveries that died meanwhile are resent one at a time with POST /webhooks/{id}/redeliver, naming the delivery_id that GET /webhooks/{id} reports.

Events

EventSent when
party.registeredA party was registered and given an ABSSIN.
transaction.gate_changedA gate on a transaction moved; the payload carries the new blocking set.
assessment.raisedAn assessment was raised, with its bill reference and payment link.
assessment.approvedAn assessment above the automatic ceiling was approved by an officer.
payment.confirmedA cited payment was matched and settled in the central ledger.
document.issuedA final document number was minted.
document.voidedA final document was voided, with the reason.
contract.awardedA contract passed its gates and was awarded; the award number is the document number.
pv.status_changedA payment voucher moved through the approval chain — raised, validated, ticked, approved, warranted.
pv.paidA payment voucher was paid, with its payment code and the verification id behind it (FR-11.5).
queue.processedA request accepted in degraded mode was finally processed.
api.deprecation_noticeAn endpoint your client uses has a sunset date.
sandbox.resetThe sandbox was reseeded; your test references are gone.

Subscribe only to what you handle. An event you did not ask for is never sent, and an unknown name in a subscription is a validation error rather than a silent no-op.