Skip to main content

Overview

Webhooks notify your application in real time when events happen on the YoID platform — a youth accepts a credential, a verification is completed, or a request expires. This eliminates the need to poll the API for status updates.
Webhooks are the recommended approach for production integrations. In the YOMA ecosystem, a youth may take minutes or days to respond to a verification request. Polling creates unnecessary load and doesn’t scale. Use webhooks as the primary pattern, and polling only as a fallback during development.

Supported Events

EventDescription
openid4vc.issuance.offeredCredential offer sent to youth
openid4vc.issuance.completedYouth accepted and received the credential
openid4vc.issuance.failedIssuance attempt failed
openid4vc.issuance.expiredCredential offer expired before youth accepted

Registering a Webhook Endpoint

curl -X POST "https://test.didxtech.com/me-creds/api/endpoints" \
  -H "Authorization: Bearer <access_token>" \
  -H "Content-Type: application/json" \
  -d '{
    "url": "https://your-domain.com/webhooks/yoid",
    "filterTypes": ["openid4vc.issuance.completed", "openid4vc.issuance.failed"]
  }'

Request Body

FieldTypeRequiredDescription
urlstringYesYour HTTPS endpoint URL
filterTypesstring[]NoArray of event types to subscribe to. If omitted, you receive all events.
Available filter types:
Event TypeDescription
openid4vc.issuance.offeredCredential offer sent to youth
openid4vc.issuance.completedYouth accepted and received the credential
openid4vc.issuance.failedIssuance attempt failed
openid4vc.issuance.expiredCredential offer expired
openid4vc.presentation.requestedVerification request created
openid4vc.presentation.verifiedYouth successfully presented credentials
openid4vc.presentation.failedVerification attempt failed
openid4vc.presentation.expiredRequest expired
Use filterTypes to reduce noise — an issuer that doesn’t verify credentials doesn’t need presentation events.

Response (201 Created)

{
  "data": {
    "id": "du398r0fqd",
    "url": "https://your-domain.com/webhooks/yoid",
    "filterTypes": ["openid4vc.issuance.completed", "openid4vc.issuance.failed"],
    "createdAt": "2025-01-01T00:00:00.000Z",
    "updatedAt": "2025-01-01T00:00:00.000Z"
  }
}

Listing Webhook Endpoints

curl -X GET "https://test.didxtech.com/me-creds/api/endpoints" \
  -H "Authorization: Bearer <access_token>"
Returns an array of all registered webhook endpoints for your organisation.

Removing a Webhook Endpoint

curl -X DELETE "https://test.didxtech.com/me-creds/api/endpoints/du398r0fqd" \
  -H "Authorization: Bearer <access_token>"
Returns 204 No Content on success.

Webhook Payload Structure

All payloads include an eventType and a payload with a resource reference:
{
  "eventType": "openid4vc.issuance.completed",
  "payload": {
    "openId4VcIssuanceId": "cmfwfjxdq003js601bmgy9tva"
  }
}
Use the resource ID to fetch the full details from the relevant API endpoint.

Technical Requirements

Your webhook endpoint must:
  • Accept POST requests
  • Use HTTPS exclusively
  • Respond promptly with a 2xx status code
  • Handle duplicate deliveries — the platform may retry on failure
  • Be idempotent — processing the same event twice should produce the same result

Implementation Example

A production-ready webhook handler for a learning partner tracking credential acceptance:
const processedWebhooks = new Set();

app.post("/webhooks/yoid", async (req, res) => {
  const webhookId = req.headers["webhook-id"];
  const { eventType, payload } = req.body;

  // Deduplicate — the platform may send the same webhook more than once
  if (processedWebhooks.has(webhookId)) {
    return res.status(200).send("OK");
  }
  processedWebhooks.add(webhookId);

  // Respond immediately, process asynchronously
  res.status(200).send("OK");

  // Handle the event
  switch (eventType) {
    case "openid4vc.issuance.completed":
      // Thandi accepted her Web Development Completion credential
      console.log(`Credential accepted: ${payload.openId4VcIssuanceId}`);
      await updateInternalRecords(payload.openId4VcIssuanceId, "accepted");
      break;

    case "openid4vc.issuance.expired":
      // Credential offer expired — youth didn't accept in time
      console.log(`Credential expired: ${payload.openId4VcIssuanceId}`);
      await flagForFollowUp(payload.openId4VcIssuanceId);
      break;

    case "openid4vc.presentation.verified":
      // Youth shared their credentials with a verifier
      console.log(`Presentation verified: ${payload.openId4VcPresentationId}`);
      break;
  }
});

Use Cases in the YOMA Ecosystem

Partner TypeEventAction
Umuzi (issuer)issuance.completedMark programme completion as “credentialed” in their LMS
Umuzi (issuer)issuance.expiredFlag youth for follow-up — they may not have activated their wallet
IXO (impact)issuance.completedRecord verified participation for funder reporting
JobJack (verifier)presentation.verifiedProceed with candidate placement
Scholarship fund (verifier)presentation.verifiedApprove eligibility and move to next application stage

Local Development

Use ngrok to expose your local server via a public HTTPS URL during development:
ngrok http 3000
Register the resulting https:// ngrok URL as your webhook endpoint. The platform retries failed deliveries automatically.
For production, consider a webhook ingestion service (like Svix or AWS EventBridge) that handles retries, logging, and replay — especially if your backend may be temporarily unavailable.