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
| Event | Description |
|---|
openid4vc.issuance.offered | Credential offer sent to youth |
openid4vc.issuance.completed | Youth accepted and received the credential |
openid4vc.issuance.failed | Issuance attempt failed |
openid4vc.issuance.expired | Credential offer expired before youth accepted |
| Event | Description |
|---|
openid4vc.presentation.requested | Verification request created |
openid4vc.presentation.verified | Youth successfully presented credentials |
openid4vc.presentation.failed | Verification attempt failed |
openid4vc.presentation.expired | Request expired before youth responded |
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
| Field | Type | Required | Description |
|---|
url | string | Yes | Your HTTPS endpoint URL |
filterTypes | string[] | No | Array of event types to subscribe to. If omitted, you receive all events. |
Available filter types:
| Event Type | Description |
|---|
openid4vc.issuance.offered | Credential offer sent to youth |
openid4vc.issuance.completed | Youth accepted and received the credential |
openid4vc.issuance.failed | Issuance attempt failed |
openid4vc.issuance.expired | Credential offer expired |
openid4vc.presentation.requested | Verification request created |
openid4vc.presentation.verified | Youth successfully presented credentials |
openid4vc.presentation.failed | Verification attempt failed |
openid4vc.presentation.expired | Request 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 Type | Event | Action |
|---|
| Umuzi (issuer) | issuance.completed | Mark programme completion as “credentialed” in their LMS |
| Umuzi (issuer) | issuance.expired | Flag youth for follow-up — they may not have activated their wallet |
| IXO (impact) | issuance.completed | Record verified participation for funder reporting |
| JobJack (verifier) | presentation.verified | Proceed with candidate placement |
| Scholarship fund (verifier) | presentation.verified | Approve eligibility and move to next application stage |
Local Development
Use ngrok to expose your local server via a public HTTPS URL during development:
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.