# Green Dot Webhooks

Green Dot Embedded Finance (Banking-as-a-Service) does not use the Standard
Webhooks spec or a single HMAC signature. It uses push authentication:
Green Dot authenticates itself to your partner-hosted endpoint. The primary
model is an OAuth 2.0 client_credentials Bearer token (scope `post:webhook`)
sent on every delivery, with a Certificate (mTLS) variant as an alternative.

## When to Use This Skill

* How do I receive Green Dot Embedded Finance / BaaS webhooks?
* How do I authenticate the Green Dot OAuth Bearer token on my endpoint?
* What is the `x-gd-signature` header and can I verify it?
* How do I echo the `x-GD-RequestId` header and return `responseDetails`?
* How do I handle `transaction`, `accountUpdated`, or `achTransfer` events?
* Why does Green Dot keep retrying my webhook endpoint?

## Verification (core)

Authenticate the delivery by validating the OAuth client_credentials Bearer
token and requiring the `post:webhook` scope. This is the real gate. Always
parse JSON after authentication passes.

```javascript
const jwt = require('jsonwebtoken');

// Authenticate: validate the OAuth client_credentials Bearer token + scope.
// In production validate against your authorization server (JWKS / RS256 or
// token introspection). HS256 with a shared program secret is shown here.
function verifyToken(authHeader) {
  const token = String(authHeader || '').replace(/^Bearer\s+/i, '').trim();
  const claims = jwt.verify(token, process.env.GREENDOT_WEBHOOK_TOKEN_SECRET);
  const scopes = String(claims.scope || claims.scp || '').split(/[\s,]+/);
  if (!scopes.includes('post:webhook')) throw new Error('missing post:webhook scope');
  return claims;
}

```

If you use the Certificate (mTLS) variant instead of OAuth, the token check
is replaced by client-certificate validation at your TLS terminator / reverse
proxy — there is no application-level token to check.

> About `x-gd-signature`: a delivery may carry an `x-gd-signature` header,
> but Green Dot's public docs do not document its algorithm, encoding, or the
> canonical payload it covers. This skill therefore does not implement a
> signature check — a guessed HMAC would give false confidence in an unverified
> payload. If you need payload-level verification, obtain the exact specification
> (and signing key) from your Green Dot representative before implementing any
> check. Authenticity comes from the OAuth Bearer token (and/or mTLS). See
> [TODO.md](https://github.com/hookdeck/webhook-skills/blob/main/skills/greendot-webhooks/TODO.md).
> 
> Green Dot also sends an `API-Key` header (your program's own static key,
> echoed back) on every delivery — it is not a signature, so don't treat it as
> proof of authenticity beyond weak defense-in-depth on top of the Bearer token.

Then echo the `x-GD-RequestId` header back and respond `200`/`201` with a
`responseDetails` body, otherwise Green Dot treats the delivery as failed:

```json
{ "responseDetails": [{ "code": 0, "subCode": 0, "description": "<x-GD-RequestId>" }] }

```

> For complete handlers with token verification, event dispatch, the
> `responseDetails` acknowledgement, and tests, see:
> 
> * [examples/express/](https://github.com/hookdeck/webhook-skills/tree/main/skills/greendot-webhooks/examples/express/)
> * [examples/nextjs/](https://github.com/hookdeck/webhook-skills/tree/main/skills/greendot-webhooks/examples/nextjs/)
> * [examples/fastapi/](https://github.com/hookdeck/webhook-skills/tree/main/skills/greendot-webhooks/examples/fastapi/)

## Common Event Types

The event name is the `eventType` field in the JSON body:

| `eventType` | Triggered when |
| --- | --- |
| `transaction` | A card or account transaction posts |
| `accountUpdated` | Account details or status change |
| `achTransfer` | An ACH transfer changes state |
| `cardUpdate` | A card is issued, activated, or its status changes |
| `billPayTransfer` | A bill pay transfer changes state |
| `directDepositSwitch` | A direct-deposit switch progresses |
| `provisioning` | Account / card provisioning progresses |

> Green Dot also emits statement-ready, interest-paid, NSF/failed-transfer, NOC,
> eWallet, paper-check, P2P, ATM PIN, and adjustment events. The exact set is
> program-specific — confirm the enabled `eventType` values with your Green
> Dot representative.

## Environment Variables

```bash
# Secret used to validate the OAuth client_credentials Bearer token (HS256).
# Shared with whoever issues Green Dot's token for your program.
GREENDOT_WEBHOOK_TOKEN_SECRET=your_token_signing_secret

# Required OAuth scope on the token (default: post:webhook).
GREENDOT_WEBHOOK_SCOPE=post:webhook

```

> The `x-gd-signature` header is not verified by this skill (its algorithm is
> undocumented — see [TODO.md](https://github.com/hookdeck/webhook-skills/blob/main/skills/greendot-webhooks/TODO.md)), so there is no signing-key environment
> variable.

## Setup Notes

* Endpoints are registered by your Green Dot representative — there is no
  self-serve dashboard. You provide the callback URL, the OAuth details, and the
  event types to enable.
* Retries must be explicitly enabled per-partner. When on, Green Dot retries
  on `5xx`, timeouts, DNS/connection/SSL failures (and `401`/`403` once the root
  cause is fixed), hourly for up to 24 hours.
* There is no official SDK — all verification is manual.

## Local Development

```bash
# Start tunnel (no account needed)
npx hookdeck-cli listen 3000 greendot --path /webhooks/greendot

```

## Reference Materials

* [references/overview.md](https://github.com/hookdeck/webhook-skills/blob/main/skills/greendot-webhooks/references/overview.md) - What Green Dot webhooks are, event types, payload shape
* [references/setup.md](https://github.com/hookdeck/webhook-skills/blob/main/skills/greendot-webhooks/references/setup.md) - Endpoint registration, OAuth, retries
* [references/verification.md](https://github.com/hookdeck/webhook-skills/blob/main/skills/greendot-webhooks/references/verification.md) - Bearer token verification, the undocumented x-gd-signature, and gotchas