iGrant.io backend webhooks (register + receive + verify)
When to use
Whenever your backend must be notified when a wallet completes an issuance or
verification. Pairs with igrantio-backend-sse (which streams the stored events
to the browser). Composed by igrantio-issuer-backend / igrantio-verifier-backend.
Before you build: run the integrator intake in igrantio-ows-overview - environment, API key, tenancy, backend host, webhooks, frontend - one question at a time, a recommended default with each.
What it does
- Register (idempotent) -
POST /v2/config/webhook with payloadUrl,
contentType, subscribedEvents.digitalWalletWebhook, secretKey. Lists
existing webhooks first and skips if one already targets the payloadUrl.
- Receive -
POST /webhook: verify X-iGrant-Signature: t=<ts>,sig=<hex>
where sig = HMAC_SHA256(secretKey, "<t>.<raw body>") (constant-time compare),
reject unknown topics, extract the exchange id, store the event.
Reference
./references:
topics.ts - ISSUER_TOPICS, VERIFIER_TOPICS, extractExchangeId(type, data).
webhooks.ts - verifySignature(...) + webhookReceiver(store) router.
registerWebhook.ts - registerWebhook({ owsBaseUrl, apiKey, payloadUrl, secretKey, topics }), idempotent.
eventStore.ts - the EventStore the receiver writes to (shared with SSE).
config.ts - webhookSecretKey, OWS base URL.
Topics → exchange id
| Topic |
Exchange id path in data |
openid.credential.offer_received / token_issued / credential_acked / credential_accepted |
credential.CredentialExchangeId |
openid.presentation.presentation_acked.v3 / digitalwallet.presentation.verified |
presentation.presentationExchangeId |
Register once per org (idempotent)
await registerWebhook({
owsBaseUrl: config.owsBaseUrl,
apiKey: "<org OWS key>",
payloadUrl: "https://your-backend/webhook",
secretKey: config.webhookSecretKey, // MUST match the receiver
topics: [...ISSUER_TOPICS], // or VERIFIER_TOPICS, or both
});
Re-running is safe: it lists existing webhooks and skips creation if the
payloadUrl is already registered.
Clean-code notes
- Signature verification is constant-time; the secret lives only in
config.
- Topic knowledge (names + exchange-id extraction) is isolated in
topics.ts.
Validation / done criteria
- A tampered body or wrong secret → 401. A supported topic with a valid signature
→ stored under its exchange id. An unsupported topic → 400.
- Registering twice creates exactly one webhook.
Documentation & workflows
When anything is unclear, consult the iGrant.io documentation before guessing:
1---2name: igrantio-backend-webhooks3description: Composable building block: register, receive, and verify iGrant.io OWS digital-wallet webhooks for OpenID4VCI issuance and OpenID4VP verification events. Idempotently create a webhook via config-create-webhook (skip if one already targets the payloadUrl), verify the X-iGrant-Signature HMAC-SHA256, map each topic to its exchange id (CredentialExchangeId / presentationExchangeId), and store the event. Use to add OWS webhook handling to any Node/TypeScript backend.4license: Apache-2.05---67# iGrant.io backend webhooks (register + receive + verify)89## When to use10Whenever your backend must be notified when a wallet completes an issuance or11verification. Pairs with `igrantio-backend-sse` (which streams the stored events12to the browser). Composed by `igrantio-issuer-backend` / `igrantio-verifier-backend`.1314**Before you build**: run the integrator intake in `igrantio-ows-overview` - environment, API key, tenancy, backend host, webhooks, frontend - one question at a time, a recommended default with each.1516## What it does17- **Register (idempotent)** - `POST /v2/config/webhook` with `payloadUrl`,18 `contentType`, `subscribedEvents.digitalWalletWebhook`, `secretKey`. Lists19 existing webhooks first and **skips if one already targets the payloadUrl**.20- **Receive** - `POST /webhook`: verify `X-iGrant-Signature: t=<ts>,sig=<hex>`21 where `sig = HMAC_SHA256(secretKey, "<t>.<raw body>")` (constant-time compare),22 reject unknown topics, extract the exchange id, store the event.2324## Reference25[`./references`](./references):26- `topics.ts` - `ISSUER_TOPICS`, `VERIFIER_TOPICS`, `extractExchangeId(type, data)`.27- `webhooks.ts` - `verifySignature(...)` + `webhookReceiver(store)` router.28- `registerWebhook.ts` - `registerWebhook({ owsBaseUrl, apiKey, payloadUrl, secretKey, topics })`, idempotent.29- `eventStore.ts` - the `EventStore` the receiver writes to (shared with SSE).30- `config.ts` - `webhookSecretKey`, OWS base URL.3132## Topics → exchange id33| Topic | Exchange id path in `data` |34| --- | --- |35| `openid.credential.offer_received` / `token_issued` / `credential_acked` / `credential_accepted` | `credential.CredentialExchangeId` |36| `openid.presentation.presentation_acked.v3` / `digitalwallet.presentation.verified` | `presentation.presentationExchangeId` |3738## Register once per org (idempotent)39```ts40await registerWebhook({41 owsBaseUrl: config.owsBaseUrl,42 apiKey: "<org OWS key>",43 payloadUrl: "https://your-backend/webhook",44 secretKey: config.webhookSecretKey, // MUST match the receiver45 topics: [...ISSUER_TOPICS], // or VERIFIER_TOPICS, or both46});47```48Re-running is safe: it lists existing webhooks and skips creation if the49payloadUrl is already registered.5051## Clean-code notes52- Signature verification is constant-time; the secret lives only in `config`.53- Topic knowledge (names + exchange-id extraction) is isolated in `topics.ts`.5455## Validation / done criteria56- A tampered body or wrong secret → 401. A supported topic with a valid signature57 → stored under its exchange id. An unsupported topic → 400.58- Registering twice creates exactly one webhook.5960## Documentation & workflows6162When anything is unclear, consult the iGrant.io documentation before guessing:6364- iGrant.io developer APIs (index): https://docs.igrant.io/docs/developer-apis65- Getting started: https://docs.igrant.io/docs/get-started/66- OpenID4VC API (issuer / verifier / webhook): https://docs.igrant.io/docs/category/openid4vc-api/issuer67- Configure a webhook: https://docs.igrant.io/docs/openid4vc-api/config-create-webhook