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