Helpers for parsing inbound C2B callback payloads posted by M-PESA to the merchant's registered validation and confirmation URLs.
Daraja does not sign callbacks. Verify requests with
Daraja.Callback.Security, deduplicate on trans_id with
Daraja.Callback.Guard, and use parse_validation/1 or
parse_confirmation/1 on untrusted input from the matching route.
Validation flow
When External Validation is enabled, M-PESA posts a validation request to
your ValidationURL before completing a transaction. You must respond within
~8 seconds using accept/0 or reject/1.
with :ok <- Daraja.Callback.Security.verify(ip: conn.remote_ip, check_ip: true, ...),
{:ok, callback} <- Daraja.C2B.Callback.parse_validation(payload) do
response =
if valid_account?(callback.bill_ref_number) do
Daraja.C2B.Callback.accept()
else
Daraja.C2B.Callback.reject("C2B00012")
end
json(conn, response)
endConfirmation flow
After a successful transaction, M-PESA posts a confirmation to your
ConfirmationURL. No response action is required — M-PESA has already
completed the transaction.
Result codes for rejection
| Code | Meaning |
|---|---|
C2B00011 | Invalid MSISDN |
C2B00012 | Invalid Account Number |
C2B00013 | Invalid Amount |
C2B00014 | Invalid KYC Details |
C2B00015 | Invalid Short Code |
C2B00016 | Other Error |
Summary
Functions
Builds the JSON response body to accept a validation request.
Parses a C2B confirmation callback map into a %Confirmation{} struct.
Parses a raw callback map into a %Validation{} or %Confirmation{} struct.
Parses a C2B validation callback map into a %Validation{} struct.
Returns :validation or :confirmation for a parsed C2B callback.
Parses a C2B callback map from an untrusted HTTP request.
Parses a C2B confirmation callback from an untrusted HTTP request.
Parses a C2B validation callback from an untrusted HTTP request.
Builds the JSON response body to reject a validation request.
Functions
Builds the JSON response body to accept a validation request.
Daraja.C2B.Callback.accept()
#=> %{"ResultCode" => "0", "ResultDesc" => "Accepted"}
@spec from_confirmation_map(map()) :: Daraja.C2B.Callback.Confirmation.t()
Parses a C2B confirmation callback map into a %Confirmation{} struct.
Use on your ConfirmationURL route.
@spec from_map(map(), :validation | :confirmation) :: Daraja.C2B.Callback.Validation.t() | Daraja.C2B.Callback.Confirmation.t()
Parses a raw callback map into a %Validation{} or %Confirmation{} struct.
Pass :validation or :confirmation to match the HTTP route that received
the callback. Do not infer message type from payload fields such as
OrgAccountBalance.
@spec from_validation_map(map()) :: Daraja.C2B.Callback.Validation.t()
Parses a C2B validation callback map into a %Validation{} struct.
Use on your ValidationURL route; do not infer validation vs confirmation
from payload fields alone.
@spec kind(Daraja.C2B.Callback.Validation.t() | Daraja.C2B.Callback.Confirmation.t()) :: :validation | :confirmation
Returns :validation or :confirmation for a parsed C2B callback.
@spec parse(map()) :: {:error, :ambiguous_callback, String.t()} | {:error, :invalid_callback, String.t()}
Parses a C2B callback map from an untrusted HTTP request.
Returns {:error, :ambiguous_callback, reason} because validation and
confirmation payloads share the same shape. Use parse_validation/1 on your
validation URL and parse_confirmation/1 on your confirmation URL instead.
@spec parse_confirmation(map()) :: {:ok, Daraja.C2B.Callback.Confirmation.t()} | {:error, :invalid_callback, String.t()}
Parses a C2B confirmation callback from an untrusted HTTP request.
Call from your ConfirmationURL route handler.
@spec parse_validation(map()) :: {:ok, Daraja.C2B.Callback.Validation.t()} | {:error, :invalid_callback, String.t()}
Parses a C2B validation callback from an untrusted HTTP request.
Call from your ValidationURL route handler.
Builds the JSON response body to reject a validation request.
result_code must be one of: "C2B00011", "C2B00012", "C2B00013",
"C2B00014", "C2B00015", "C2B00016". Defaults to "C2B00016" ("Other
Error") if an unrecognised code is supplied.
Daraja.C2B.Callback.reject("C2B00012")
#=> %{"ResultCode" => "C2B00012", "ResultDesc" => "Invalid Account Number"}