Skip to content

Webhooks API ​

Webhooks notify your application in real time when things happen to your credentials — when one is issued, delivered, revoked, or viewed. Instead of polling the API, you register an HTTPS endpoint and badges.ninja sends it a signed POST for each event you subscribe to.

All management endpoints require authentication via the X-Api-Key header. See Authentication. Creating or deleting endpoints requires a key with write scope; listing requires read. See API Keys.

Register an Endpoint ​

POST /webhooks

Parameters ​

ParameterTypeRequiredDescription
urlstringYesYour HTTPS endpoint. Must start with https://.
eventsstring[]NoEvent types to subscribe to. Omit or pass ["*"] to receive all events.

Example ​

bash
curl -X POST https://api.badges.ninja/webhooks \
  -H "X-Api-Key: bws_your_api_key_here" \
  -H "Content-Type: application/json" \
  -d '{
    "url": "https://example.com/hooks/badges",
    "events": ["credential.issued", "credential.delivered"]
  }'

Response ​

201 Created. The signing secret is returned only once — store it now; you cannot retrieve it again.

json
{
  "id": "e4b19ff5-063d-4799-bd75-d03641be624f",
  "url": "https://example.com/hooks/badges",
  "events": ["credential.issued", "credential.delivered"],
  "secret": "2be6d335682c1658242fde3a523fd8a2493bab8c"
}

List Endpoints ​

GET /webhooks

Returns your registered endpoints. Signing secrets are never included.

json
{
  "webhooks": [
    {
      "id": "e4b19ff5-063d-4799-bd75-d03641be624f",
      "url": "https://example.com/hooks/badges",
      "events": ["credential.issued", "credential.delivered"],
      "active": true,
      "failureCount": 0,
      "createdAt": 1787685415083,
      "lastStatus": 200,
      "lastDeliveryAt": 1787685480777
    }
  ]
}

Delete an Endpoint ​

DELETE /webhooks/{id}
bash
curl -X DELETE https://api.badges.ninja/webhooks/e4b19ff5-063d-4799-bd75-d03641be624f \
  -H "X-Api-Key: bws_your_api_key_here"

Deliveries to the endpoint stop immediately.

Event Types ​

EventFires when…
credential.issuedA credential is issued to a recipient.
credential.deliveredA credential's notification email is sent to the recipient (single or bulk).
credential.revokedA credential is revoked.
credential.viewedA recipient's public credential page is viewed for the first time.

Delivery Payload ​

Every delivery is a POST with a JSON body of this shape:

json
{
  "id": "b1c2d3e4-...",
  "type": "credential.issued",
  "createdAt": 1787685415083,
  "data": { }
}
FieldDescription
idUnique delivery id (use it to de-duplicate).
typeThe event type.
createdAtEvent timestamp (epoch milliseconds).
dataEvent-specific payload (see below).

data by event type ​

credential.issued

json
{
  "awardId": "https://api.badges.ninja/certify-badge/award/<guid>",
  "badgeId": "https://api.badges.ninja/certify-badge/badge/<guid>",
  "badgeName": "Advanced Certification",
  "recipient": { "email": "jane@example.com", "name": "Jane Doe" },
  "issuedOn": "2026-08-25",
  "expires": null
}

credential.delivered

json
{
  "awardId": "https://api.badges.ninja/certify-badge/award/<guid>",
  "recipient": { "email": "jane@example.com", "name": "Jane Doe" },
  "badgeName": "Advanced Certification"
}

credential.revoked

json
{ "awardId": "https://api.badges.ninja/certify-badge/award/<guid>", "reason": "Issued in error" }

credential.viewed

json
{
  "awardId": "https://api.badges.ninja/certify-badge/award/<guid>",
  "recipient": { "email": "jane@example.com", "name": "Jane Doe" }
}

Request Headers ​

Each delivery carries these headers:

HeaderDescription
X-Bws-EventThe event type (same as type in the body).
X-Bws-DeliveryThe delivery id (same as id in the body).
X-Bws-Signaturesha256= followed by the HMAC-SHA256 of the raw request body, keyed by your endpoint secret.
User-Agentbadges.ninja-webhooks/1

Verifying Signatures ​

Always verify the signature before trusting a delivery. Compute the HMAC-SHA256 of the raw request body using your endpoint's signing secret, and compare it (in constant time) to the X-Bws-Signature header.

js
import crypto from "node:crypto";

function verify(rawBody, signatureHeader, secret) {
  const expected = "sha256=" +
    crypto.createHmac("sha256", secret).update(rawBody, "utf8").digest("hex");
  const a = Buffer.from(signatureHeader);
  const b = Buffer.from(expected);
  return a.length === b.length && crypto.timingSafeEqual(a, b);
}

Use the exact bytes you received — parsing and re-serializing the JSON first will change the bytes and break the signature.

Retries & Reliability ​

  • A delivery is considered successful when your endpoint responds with a 2xx status.
  • Failed deliveries are retried a short number of times. Endpoints that keep failing accumulate a failureCount; after 20 consecutive failures an endpoint is automatically disabled (active: false) and stops receiving deliveries until you fix it and register a new endpoint.
  • Deliveries can arrive more than once. Use the X-Bws-Delivery id (or the body id) to make your handler idempotent.
  • Respond quickly (within ~10 seconds). Do heavy work asynchronously after acknowledging.

Managing Webhooks in the Dashboard ​

You can also manage endpoints without the API — open Webhooks in the dashboard sidebar to add, list, and delete endpoints and choose which events each one receives.

badges.ninja Documentation