Skip to main content
POST
https://your.endpoint.to.notify
Callback Charge
curl --request POST \
  --url https://your.endpoint.to.notify/ \
  --header 'Authorization: Bearer <token>'
{
  "id": 123,
  "invoice": "<string>",
  "status": {
    "id": 123,
    "name": "<string>"
  },
  "status_detail": {
    "code": "<string>",
    "detail": "<string>"
  },
  "updated_at": "<string>",
  "metadata": {
    "paid_amount": 123,
    "payer": {
      "bank_payer": {}
    }
  }
}

Callbacks

Receive updates about your payin.

Cash-in

Endpoint: POST https://your.endpoint.to.notifyThis is your endpoint where WEpayments will send notifications about payin status changes.

Response

You should respond with 200 OK to acknowledge receipt of the webhook.

Body

The webhook payload contains the following fields:
id
integer
Cash-in IDExample: 49339
invoice
string
Cash-in invoiceExample: eb21ce52-2897-475b-85af-a5201f4035bf
status
object
Object that contains the current status of your payin.
status_detail
object
This object will only be returned if the payin is rejected and the merchant is using the “Account Mismatch” functionality. It provides more details on the reason for the rejection.
updated_at
string
Last update of your payin.Format: <date-time>Example: 2024-09-09T20:55:56.000000Z
metadata
object
Additional metadata about the payment.

Webhook Payload Example

{
  "id": 49339,
  "invoice": "eb21ce52-2897-475b-85af-a5201f4035bf",
  "status": {
    "id": 7,
    "name": "Rejected"
  },
  "status_detail": {
    "code": "WE0001",
    "detail": "The payment was made from an unregistered account."
  },
  "updated_at": "2024-09-09T20:55:56.000000Z",
  "metadata": {
    "paid_amount": 9.9,
    "payer": {
      "bank_payer": {
        "bank_code": "001",
        "branch": "1234",
        "account": "12345678",
        "account_type": "checking",
        "holder_name": "Customer Name",
        "holder_document": "12345678900"
      }
    }
  }
}

Status Codes

Common payin status codes:
Status IDStatus NameDescription
1CreatedCharge has been created
2CanceledCharge canceled due to expiration
3RejectedCharge rejected due to compliance
4PaidCharge successfully paid
5CreditedAmount is paid and available for withdrawal
6Drop_requestedCancellation requested
See Cash-in Status Flow for detailed status information.

Implementing the Webhook Endpoint

const express = require('express');
const app = express();

app.use(express.json());

app.post('/webhook/payin', (req, res) => {
  const { id, invoice, status, status_detail, updated_at, metadata } = req.body;
  
  console.log(`Received payin webhook for ID: ${id}`);
  console.log(`Invoice: ${invoice}`);
  console.log(`Status: ${status.name} (${status.id})`);
  console.log(`Updated at: ${updated_at}`);
  
  // Handle different statuses
  switch (status.id) {
    case 5: // Credited
      console.log('✓ Payment successful and credited');
      handleSuccessfulPayment(id, invoice, metadata);
      break;
      
    case 3: // Rejected
      console.log('✗ Payment rejected');
      if (status_detail) {
        console.log(`Reason: ${status_detail.code} - ${status_detail.detail}`);
      }
      handleRejectedPayment(id, invoice, status_detail);
      break;
      
    case 2: // Canceled
      console.log('⊘ Payment canceled or expired');
      handleCanceledPayment(id, invoice);
      break;
      
    default:
      console.log(`Status: ${status.name}`);
  }
  
  // Always respond 200 OK
  res.status(200).json({ received: true });
});

function handleSuccessfulPayment(id, invoice, metadata) {
  // Verify webhook signature before using paid_amount
  const paidAmount = metadata?.paid_amount;
  
  // Update order status
  updateOrderStatus(invoice, 'paid', paidAmount);
  
  // Send confirmation email
  sendConfirmationEmail(invoice);
  
  // Trigger fulfillment
  triggerFulfillment(invoice);
}

function handleRejectedPayment(id, invoice, statusDetail) {
  // Log rejection reason
  console.error(`Rejection: ${statusDetail?.code} - ${statusDetail?.detail}`);
  
  // Notify customer
  notifyCustomer(invoice, 'rejected', statusDetail);
  
  // Update order status
  updateOrderStatus(invoice, 'payment_failed');
}

function handleCanceledPayment(id, invoice) {
  // Update order status
  updateOrderStatus(invoice, 'canceled');
  
  // Notify customer
  notifyCustomer(invoice, 'canceled');
}

app.listen(3000, () => {
  console.log('Webhook server listening on port 3000');
});

Best Practices

Always Respond 200 OK: Your endpoint must respond with HTTP 200 to acknowledge receipt. Otherwise, WEpayments will retry sending the webhook.
Validate Webhook Signature: Always validate the webhook signature to ensure it’s from WEpayments. See Webhook Signature Documentation for details.
Verify paid_amount: Since the paid_amount parameter contains sensitive financial information, only use this data if you are verifying the webhook signature.
Process Asynchronously: Handle webhook processing asynchronously to respond quickly and avoid timeouts.

Webhook Retry Logic

If your endpoint doesn’t respond with 200 OK, WEpayments will retry sending the webhook:
  • 1st retry: After 1 minute
  • 2nd retry: After 5 minutes
  • 3rd retry: After 15 minutes
  • 4th retry: After 1 hour
  • 5th retry: After 6 hours
After 5 failed attempts, the webhook will be marked as failed and no more retries will be attempted.

Testing Webhooks

1

Set Up Local Endpoint

Create a webhook endpoint in your local development environment.
2

Use Ngrok or Similar

Use a tool like ngrok to expose your local endpoint to the internet:
ngrok http 3000
3

Configure Webhook URL

Use the ngrok URL when creating a charge with notification_url.
4

Test Different Scenarios

Create test charges and observe the webhooks received for different statuses.