PayGate

Events

Retrieve and manage webhook events.

Events API

Events represent things that happen in your PayGate account, such as a payment succeeding or a subscription being canceled. When an event occurs, PayGate sends it to your webhook endpoints.

The Event Object

{
  "id": "evt_abc123def456",
  "object": "event",
  "type": "payment.succeeded",
  "created_at": "2024-01-15T10:30:00Z",
  "data": {
    "object": {
      "id": "pay_xyz789",
      "object": "payment",
      "amount": 5000,
      "currency": "GHS",
      "status": "succeeded",
      "payment_method": "mobile_money",
      "provider": "mtn",
      "phone": "024****567",
      ...
    }
  },
  "pending_webhooks": 1,
  "request": {
    "id": "req_abc123",
    "idempotency_key": "order_1234"
  }
}

Attributes

AttributeTypeDescription
idstringUnique identifier
objectstringAlways "event"
typestringEvent type
created_atstringWhen event was created
dataobjectEvent data
data.objectobjectThe object that triggered the event
pending_webhooksintegerWebhooks pending delivery
requestobjectAPI request that caused this event

Event Types

Payment Events

EventDescription
payment.createdPayment was created
payment.pendingPayment is pending authorization
payment.processingPayment is being processed
payment.succeededPayment completed successfully
payment.failedPayment failed
payment.canceledPayment was canceled

Subscription Events

EventDescription
subscription.createdSubscription was created
subscription.updatedSubscription was updated
subscription.canceledSubscription was canceled
subscription.pausedSubscription was paused
subscription.resumedSubscription was resumed
subscription.trial_will_endTrial ending in 3 days
subscription.payment_succeededRecurring payment succeeded
subscription.payment_failedRecurring payment failed

Payout Events

EventDescription
payout.createdPayout was created
payout.processingPayout is being processed
payout.completedPayout was successful
payout.failedPayout failed
payout.canceledPayout was canceled

Refund Events

EventDescription
refund.createdRefund was created
refund.succeededRefund was successful
refund.failedRefund failed

Checkout Events

EventDescription
checkout.session.completedCheckout session completed
checkout.session.expiredCheckout session expired

Customer Events

EventDescription
customer.createdCustomer was created
customer.updatedCustomer was updated
customer.deletedCustomer was deleted

Retrieve an Event

Retrieves an event by ID.

GET /v1/events/:id
curl https://api.44.200.142.19.nip.io/v1/events/evt_abc123def456 \
  -H "Authorization: Bearer sk_test_..."
const event = await paygate.events.retrieve('evt_abc123def456')

console.log(event.type) // 'payment.succeeded'
console.log(event.data.object.id) // 'pay_xyz789'
event = client.events.retrieve('evt_abc123def456')

print(event.type)  # 'payment.succeeded'
print(event.data.object.id)  # 'pay_xyz789'

List Events

Returns a list of events.

GET /v1/events

Query Parameters

ParameterTypeDescription
limitintegerNumber of results (1-100)
starting_afterstringCursor for pagination
ending_beforestringCursor for pagination
typestringFilter by event type
created[gte]stringFilter by creation date
created[lte]stringFilter by creation date
curl "https://api.44.200.142.19.nip.io/v1/events?type=payment.succeeded&limit=10" \
  -H "Authorization: Bearer sk_test_..."
const events = await paygate.events.list({
  type: 'payment.succeeded',
  limit: 10
})

for (const event of events.data) {
  console.log(event.id, event.type)
}
events = client.events.list(type='payment.succeeded', limit=10)

for event in events.data:
    print(event.id, event.type)

Response

{
  "object": "list",
  "data": [
    {
      "id": "evt_abc123def456",
      "object": "event",
      "type": "payment.succeeded",
      "created_at": "2024-01-15T10:30:00Z",
      "data": {
        "object": {...}
      }
    }
  ],
  "has_more": true,
  "url": "/v1/events"
}

Handling Events

Events are delivered to your webhook endpoints. Here's how to handle them:

import express from 'express'
import { Webhook } from '@paygate/node'

const app = express()
const endpointSecret = 'whsec_...'

app.post('/webhooks', express.raw({ type: 'application/json' }), (req, res) => {
  const signature = req.headers['x-paygate-signature']

  let event

  try {
    event = Webhook.constructEvent(req.body, signature, endpointSecret)
  } catch (err) {
    console.error('Webhook signature verification failed:', err.message)
    return res.status(400).send('Invalid signature')
  }

  // Handle the event
  switch (event.type) {
    case 'payment.succeeded':
      const payment = event.data.object
      await handlePaymentSuccess(payment)
      break

    case 'payment.failed':
      const failedPayment = event.data.object
      await handlePaymentFailure(failedPayment)
      break

    case 'subscription.created':
      const subscription = event.data.object
      await handleNewSubscription(subscription)
      break

    case 'subscription.canceled':
      const canceledSub = event.data.object
      await handleCanceledSubscription(canceledSub)
      break

    default:
      console.log(`Unhandled event type: ${event.type}`)
  }

  res.json({ received: true })
})

Best Practices

1. Handle Events Idempotently

Events may be delivered more than once. Use the event ID to avoid processing duplicates:

async function handleEvent(event) {
  // Check if already processed
  if (await isEventProcessed(event.id)) {
    return
  }

  // Process the event
  await processEvent(event)

  // Mark as processed
  await markEventProcessed(event.id)
}

2. Return 200 Quickly

Acknowledge webhooks quickly, then process asynchronously:

app.post('/webhooks', (req, res) => {
  // Verify signature
  const event = Webhook.constructEvent(...)

  // Acknowledge immediately
  res.json({ received: true })

  // Process async
  queueEventForProcessing(event)
})

3. Use Event Types Effectively

Listen only for events you care about:

// When creating webhook endpoint
const endpoint = await paygate.webhooks.endpoints.create({
  url: 'https://yoursite.com/webhooks',
  events: [
    'payment.succeeded',
    'payment.failed',
    'subscription.created',
    'subscription.canceled'
  ]
})

You can also use * to receive all events, but this isn't recommended for production.


Retry Events

If you need to reprocess an event, you can fetch it from the API:

// Fetch a specific event
const event = await paygate.events.retrieve('evt_abc123def456')

// Reprocess it
await handleEvent(event)

Or fetch recent events of a specific type:

// Get all payment.succeeded events from the last hour
const oneHourAgo = new Date(Date.now() - 60 * 60 * 1000)

const events = await paygate.events.list({
  type: 'payment.succeeded',
  created: { gte: oneHourAgo.toISOString() }
})

for (const event of events.data) {
  await handleEvent(event)
}