Capability request webhooks

Every API call that moves a capability request, the event it emits, and the payload your handler receives.

Who this section is for

Developers who open capability requests over the API instead of the merchant dashboard, and
need to know when PayMongo has reviewed one. Everything the dashboard can do here is available
as an API call, and every status change the dashboard shows is also pushed as a webhook event.

A capability request is an application to turn something on for an account: a payment method
(GCash, Maya, GrabPay, ShopeePay, cards), account activation, a standard wallet, a tax document
filing, or a change to the business profile. PayMongo reviews it, and the request moves through a
fixed set of statuses. Each move emits one capability_request.* event.

📘

Related but different

Account onboarding events — identity verification and activation of a child account — are a
separate family. See Onboarding webhooks.

Overview

Triggered by your API calls

API callNew statusEvent emitted
POST /v1/requestsdraft(none — a create is not a status change)
POST /v1/requests/:id/submitsubmittedcapability_request.submitted
POST /v1/requests/:id/reviewunder_reviewcapability_request.under_review
POST /v1/requests/:id/review/finishprocessingcapability_request.processing
POST /v1/requests/:id/resubmitunder_reviewcapability_request.under_review
POST /v1/requests/:id/cancelcancelledcapability_request.cancelled

Triggered by PayMongo review

No API call on your side — these arrive as webhooks only.

New statusEvent emitted
pending_clarificationcapability_request.pending_clarification
processingcapability_request.processing
completedcapability_request.completed
declinedcapability_request.declined
📘

Looking for the endpoints?

Creating a request, submitting, resubmitting, cancelling, and the full product code list are
documented in Capability requests API under
Onboarding-as-a-Service. This page covers only what PayMongo pushes back to you.

Status lifecycle:

flowchart LR
  A[draft] -->|submit| B[submitted]
  B -->|review| C[under_review]
  C -->|needs info| D[pending_clarification]
  D -->|resubmit| C
  C -->|approved| E[processing]
  E --> F[completed]
  C -->|rejected| G[declined]

Entry points. Most requests start in draft and move on submit, but payment method,
account activation and tax document requests are created directly in submitted.

Terminal statuses. completed, declined and cancelled. A declined request can only be
reopened by PayMongo, which re-emits capability_request.submitted on the same request ID.

Cancelling. Allowed from draft, submitted, under_review and pending_clarification
these paths are left off the diagram to keep it readable. Cancel is rejected from processing,
because the capability is already being provisioned, and from all three terminal statuses.

Before you begin

🚧

Prerequisites

  1. Register an HTTPS endpoint and verify its signatures — see
    Setup & Management.
  2. Subscribe to the capability_request.* events you need. All seven are listed in the
    event catalog below.
  3. Authenticate API calls with the account's secret key using HTTP Basic auth
    (Authorization: Basic base64(sk_xxx:)).
⚠️

Register on the account that owns the request

Events are delivered to the webhook endpoints registered on the account named in
data.account_id
— the account the capability is being requested for, which is the account
whose secret key created the request. Registering only on a parent account will not receive a
child's capability request events.

Match the mode too. An event's livemode is fixed by the key that opened the request — a
request created with a live secret key emits livemode: true for its whole lifecycle, one
created with a test key emits livemode: false. A webhook only receives events of its own
mode, and a webhook's mode is set by the key that registered it. So a request opened live needs
a webhook registered live.

This holds for every later transition too, including the ones PayMongo drives during review —
the mode is recorded when the request is created and does not change afterwards.

Event catalog

Listed in lifecycle order. Only completed, declined and cancelled are terminal.

EventFires whenYour move
capability_request.submittedThe request was submitted for review from draft, or a declined request was reopened by PayMongo.Nothing.
capability_request.under_reviewA reviewer has picked up the request, or the merchant answered a clarification request.Show "in review".
capability_request.pending_clarificationThe reviewer needs more information. Read data.merchant_note for what to supply.Collect what was asked for, PATCH, then resubmit.
capability_request.processingReview passed and PayMongo is provisioning the capability. No further merchant action.Nothing.
capability_request.completedThe request was approved and applied.Mark the capability live. Terminal.
capability_request.declinedThe request was rejected. Read data.merchant_note for the reason.Show the reason. Terminal — open a new request to retry.
capability_request.cancelledThe request was withdrawn before review finished.The product code is free again. Terminal.

Payload structure

Every event shares the standard envelope. attributes.data is the capability request resource
itself
— the same object GET /v1/requests/{id} returns. There is no separate event-shaped
body, so one parser handles both the polled and the pushed copy.

FieldTypeDescription
data.idstringEvent ID. Prefixed evt_.
data.typestringAlways event.
data.attributes.typestringThe event type, e.g. capability_request.completed.
data.attributes.livemodebooleantrue when the request was opened with a live secret key, false for a test key. Fixed at creation and constant for the request's whole lifecycle.
data.attributes.dataobjectThe capability request resource. See Resource fields.
data.attributes.previous_dataobjectAlways {} for these events.
data.attributes.created_atintegerUnix timestamp of the transition.
data.attributes.updated_atintegerUnix timestamp of the transition.

The transition is described by attributes.type together with the resource's own status and
its per-transition timestamp (processed_at, completed_at, …). There is no previous_status
field — if you need the prior status, keep the last event you processed for that request ID.

Event samples

capability_request.submitted

codeSample payload
{
  "data": {
    "id": "evt_xxxxxxxxxxxxxxxxxxxxxxxx",
    "type": "event",
    "attributes": {
      "type": "capability_request.submitted",
      "livemode": true,
      "data": {
        "id": "req_xxxxxxxxxxxxxxxxxxxxxxxx",
        "account_id": "acct_xxxxxxxxxxxxxxxxxxxxxxxx",
        "request_type": "onboarding",
        "product_code": "prd_svc_paymongo_std_wallet",
        "status": "submitted",
        "kyb_data": { "business_industry": "retail" },
        "merchant_note": {},
        "submitted_at": "2026-08-24T01:55:00Z",
        "created_at": "2026-08-24T01:40:00Z",
        "updated_at": "2026-08-24T01:55:00Z"
      },
      "previous_data": {},
      "pending_webhooks": 1,
      "created_at": 1787882100,
      "updated_at": 1787882100
    }
  }
}

What to do: nothing. Note this fires on POST /submit from draft, and when PayMongo
reopens a declined request — not on POST /v1/requests.

capability_request.under_review

codeSample payload
{
  "data": {
    "id": "evt_xxxxxxxxxxxxxxxxxxxxxxxx",
    "type": "event",
    "attributes": {
      "type": "capability_request.under_review",
      "livemode": true,
      "data": {
        "id": "req_xxxxxxxxxxxxxxxxxxxxxxxx",
        "account_id": "acct_xxxxxxxxxxxxxxxxxxxxxxxx",
        "request_type": "product_request",
        "product_code": "prd_svc_paymongo_ewallet_maya",
        "status": "under_review",
        "kyb_data": {},
        "merchant_note": {},
        "submitted_at": "2026-08-24T02:10:11Z",
        "assigned_at": "2026-08-24T03:04:57Z",
        "created_at": "2026-08-24T02:10:11Z",
        "updated_at": "2026-08-24T03:04:57Z"
      },
      "previous_data": {},
      "pending_webhooks": 1,
      "created_at": 1787886297,
      "updated_at": 1787886297
    }
  }
}

What to do: nothing. Surface "in review" in your UI.

capability_request.pending_clarification

codeSample payload
{
  "data": {
    "id": "evt_xxxxxxxxxxxxxxxxxxxxxxxx",
    "type": "event",
    "attributes": {
      "type": "capability_request.pending_clarification",
      "livemode": true,
      "data": {
        "id": "req_xxxxxxxxxxxxxxxxxxxxxxxx",
        "account_id": "acct_xxxxxxxxxxxxxxxxxxxxxxxx",
        "request_type": "product_request",
        "product_code": "prd_svc_paymongo_ewallet_maya",
        "status": "pending_clarification",
        "kyb_data": {},
        "merchant_note": {
          "note": "The uploaded valid ID is expired. Please upload a current one.",
          "set_at": "2026-08-24T05:41:02Z",
          "requested_files": ["valid_id"]
        },
        "submitted_at": "2026-08-24T02:10:11Z",
        "assigned_at": "2026-08-24T03:04:57Z",
        "pending_clarification_at": "2026-08-24T05:41:02Z",
        "created_at": "2026-08-24T02:10:11Z",
        "updated_at": "2026-08-24T05:41:02Z"
      },
      "previous_data": {},
      "pending_webhooks": 1,
      "created_at": 1787895662,
      "updated_at": 1787895662
    }
  }
}

What to do: show merchant_note.note to the merchant. merchant_note.requested_fields
lists kyb_data keys to resupply and merchant_note.requested_files lists document purposes to
re-upload. Then PATCH the request if needed and call POST /v1/requests/{id}/resubmit.

📘

The clarification loop repeats

pending_clarification → under_review → pending_clarification is a normal cycle, so you can
receive the same event type for the same request more than once. Distinguish occurrences by
attributes.created_at and data.pending_clarification_at, not by event type alone.

capability_request.processing

codeSample payload
{
  "data": {
    "id": "evt_xxxxxxxxxxxxxxxxxxxxxxxx",
    "type": "event",
    "attributes": {
      "type": "capability_request.processing",
      "livemode": true,
      "data": {
        "id": "req_xxxxxxxxxxxxxxxxxxxxxxxx",
        "account_id": "acct_xxxxxxxxxxxxxxxxxxxxxxxx",
        "request_type": "product_request",
        "product_code": "prd_svc_paymongo_ewallet_maya",
        "status": "processing",
        "kyb_data": {},
        "merchant_note": {},
        "submitted_at": "2026-08-24T02:10:11Z",
        "assigned_at": "2026-08-24T03:04:57Z",
        "processed_at": "2026-08-24T06:02:44Z",
        "created_at": "2026-08-24T02:10:11Z",
        "updated_at": "2026-08-24T06:02:44Z"
      },
      "previous_data": {},
      "pending_webhooks": 1,
      "created_at": 1787896964,
      "updated_at": 1787896964
    }
  }
}

What to do: nothing — and do not treat this as approval. The request can no longer be
cancelled from here, but the capability is not usable until completed.

capability_request.completed

codeSample payload
{
  "data": {
    "id": "evt_xxxxxxxxxxxxxxxxxxxxxxxx",
    "type": "event",
    "attributes": {
      "type": "capability_request.completed",
      "livemode": true,
      "data": {
        "id": "req_xxxxxxxxxxxxxxxxxxxxxxxx",
        "account_id": "acct_xxxxxxxxxxxxxxxxxxxxxxxx",
        "request_type": "product_request",
        "product_code": "prd_svc_paymongo_ewallet_maya",
        "status": "completed",
        "kyb_data": {},
        "merchant_note": {},
        "submitted_at": "2026-08-24T02:10:11Z",
        "assigned_at": "2026-08-24T03:04:57Z",
        "processed_at": "2026-08-24T06:02:44Z",
        "completed_at": "2026-08-24T06:19:30Z",
        "created_at": "2026-08-24T02:10:11Z",
        "updated_at": "2026-08-24T06:19:30Z"
      },
      "previous_data": {},
      "pending_webhooks": 1,
      "created_at": 1787897970,
      "updated_at": 1787897970
    }
  }
}

What to do: mark the capability as approved. For a payment method, confirm it is live by
reading the account's enabled payment methods before routing traffic to it — approval and the
provider's own configuration can settle a short time apart.

capability_request.declined

codeSample payload
{
  "data": {
    "id": "evt_xxxxxxxxxxxxxxxxxxxxxxxx",
    "type": "event",
    "attributes": {
      "type": "capability_request.declined",
      "livemode": true,
      "data": {
        "id": "req_xxxxxxxxxxxxxxxxxxxxxxxx",
        "account_id": "acct_xxxxxxxxxxxxxxxxxxxxxxxx",
        "request_type": "product_request",
        "product_code": "prd_svc_paymongo_ewallet_maya",
        "status": "declined",
        "kyb_data": {},
        "merchant_note": {
          "note": "The business industry is not supported for this payment method.",
          "set_at": "2026-08-24T06:31:08Z"
        },
        "submitted_at": "2026-08-24T02:10:11Z",
        "assigned_at": "2026-08-24T03:04:57Z",
        "declined_at": "2026-08-24T06:31:08Z",
        "created_at": "2026-08-24T02:10:11Z",
        "updated_at": "2026-08-24T06:31:08Z"
      },
      "previous_data": {},
      "pending_webhooks": 1,
      "created_at": 1787898668,
      "updated_at": 1787898668
    }
  }
}

What to do: show merchant_note.note. A declined request is terminal — open a new request if
the underlying reason is resolved. If you believe a decline was made in error, contact
[email protected]. A decline that PayMongo reverses arrives as
capability_request.submitted on the same request ID.

capability_request.cancelled

codeSample payload
{
  "data": {
    "id": "evt_xxxxxxxxxxxxxxxxxxxxxxxx",
    "type": "event",
    "attributes": {
      "type": "capability_request.cancelled",
      "livemode": true,
      "data": {
        "id": "req_xxxxxxxxxxxxxxxxxxxxxxxx",
        "account_id": "acct_xxxxxxxxxxxxxxxxxxxxxxxx",
        "request_type": "product_request",
        "product_code": "prd_svc_paymongo_ewallet_maya",
        "status": "cancelled",
        "kyb_data": {},
        "merchant_note": {},
        "submitted_at": "2026-08-24T02:10:11Z",
        "cancelled_at": "2026-08-24T04:00:00Z",
        "created_at": "2026-08-24T02:10:11Z",
        "updated_at": "2026-08-24T04:00:00Z"
      },
      "previous_data": {},
      "pending_webhooks": 1,
      "created_at": 1787889600,
      "updated_at": 1787889600
    }
  }
}

What to do: the product code is free again — a new request for it will now be accepted.

Resource fields

📘

Fields you will not see

requester_id, assignee_email and internal_note are internal to PayMongo's review process
and are removed from everything a merchant receives — the public endpoints and this webhook
alike. They appear in the OpenAPI reference only under the internal request schema, never under
the merchant one, so build against the fields listed below.

The account a request belongs to is account_id. That is the only identity in the payload, and
it is all a merchant needs.

Fields of data.attributes.data. Timestamp fields are RFC 3339 strings and are omitted until
the corresponding transition happens
, so their presence is itself a signal.

FieldTypeDescription
idstringCapability request ID. Prefixed req_.
account_idstringThe account the capability is requested for, and the account the event is delivered to.
request_typestringonboarding, product_request, config_update, or account_update.
product_codestringThe capability requested. See Product codes.
statusstringdraft, submitted, under_review, pending_clarification, processing, completed, declined, cancelled.
kyb_dataobjectApplication data as submitted. {} for payment method activation.
merchant_noteobjectReviewer note. {} when none has been left.
merchant_note.notestringHuman-readable reason, on declined and pending_clarification.
merchant_note.set_atstringWhen the note was written.
merchant_note.requested_fieldsarraykyb_data keys the reviewer asked you to resupply. Absent when none were named.
merchant_note.requested_filesarrayDocument purposes the reviewer asked you to re-upload. Absent when none were named.
organization_dataobjectProfile snapshot, present on some product codes.
submitted_atstringWhen the request entered submitted.
assigned_atstringWhen a PayMongo reviewer picked the request up. Absent while it waits in the queue, and cleared if it is returned to the queue. A resubmission does not change it.
pending_clarification_atstringWhen the reviewer last asked for clarification.
processed_atstringWhen provisioning started.
completed_atstringWhen the request was approved.
declined_atstringWhen the request was declined.
cancelled_atstringWhen the request was cancelled.
created_atstringWhen the request was created.
updated_atstringWhen the request last changed.

Internal review fields — the assigned reviewer's identity, internal notes, and the audit trail —
are never included. assigned_at is the one review detail you do receive: it is a timestamp
only, and names nobody.

What does not emit an event

  • Creating a request. Not a status change. Read the create response.
  • Reading a request. GET calls emit nothing.
  • A PATCH that does not move the status. Editing kyb_data while a request sits in
    pending_clarification emits nothing until you call resubmit.
  • Internal reassignment. Which PayMongo reviewer holds a request is not merchant-visible and
    emits no event.

Delivery and retries

Events are delivered as HTTPS POST requests, signed with the Paymongo-Signature header.
Respond 2xx to acknowledge. PayMongo retries up to twelve times with exponential backoff
for any other response, and a webhook that fails three deliveries in a row is disabled
automatically. For signing keys and verification examples, see the
Webhook resource and Securing webhooks.

Delivery is at-least-once and ordering is not guaranteed. Make handlers idempotent on
data.id, and prefer data.attributes.data.status over arrival order when deciding the current
state — or re-read GET /v1/requests/{id}, which returns the same object.

Related pages


Did this page help you?